@actalk/inkos-core 1.3.12 → 1.4.0

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.
Files changed (70) hide show
  1. package/dist/agent/agent-session.d.ts.map +1 -1
  2. package/dist/agent/agent-session.js +6 -4
  3. package/dist/agent/agent-session.js.map +1 -1
  4. package/dist/agent/agent-system-prompt.d.ts.map +1 -1
  5. package/dist/agent/agent-system-prompt.js +32 -12
  6. package/dist/agent/agent-system-prompt.js.map +1 -1
  7. package/dist/agent/agent-tools.d.ts +27 -0
  8. package/dist/agent/agent-tools.d.ts.map +1 -1
  9. package/dist/agent/agent-tools.js +167 -1
  10. package/dist/agent/agent-tools.js.map +1 -1
  11. package/dist/agents/length-normalizer.d.ts +1 -0
  12. package/dist/agents/length-normalizer.d.ts.map +1 -1
  13. package/dist/agents/length-normalizer.js +24 -3
  14. package/dist/agents/length-normalizer.js.map +1 -1
  15. package/dist/agents/short-fiction.d.ts +105 -0
  16. package/dist/agents/short-fiction.d.ts.map +1 -0
  17. package/dist/agents/short-fiction.js +276 -0
  18. package/dist/agents/short-fiction.js.map +1 -0
  19. package/dist/agents/writer.d.ts +1 -0
  20. package/dist/agents/writer.d.ts.map +1 -1
  21. package/dist/agents/writer.js +53 -27
  22. package/dist/agents/writer.js.map +1 -1
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/interaction/session-transcript-restore.d.ts.map +1 -1
  28. package/dist/interaction/session-transcript-restore.js +4 -0
  29. package/dist/interaction/session-transcript-restore.js.map +1 -1
  30. package/dist/interaction/session.d.ts +22 -0
  31. package/dist/interaction/session.d.ts.map +1 -1
  32. package/dist/interaction/session.js +1 -0
  33. package/dist/interaction/session.js.map +1 -1
  34. package/dist/llm/cover-providers.d.ts +13 -0
  35. package/dist/llm/cover-providers.d.ts.map +1 -0
  36. package/dist/llm/cover-providers.js +33 -0
  37. package/dist/llm/cover-providers.js.map +1 -0
  38. package/dist/llm/providers/index.d.ts.map +1 -1
  39. package/dist/llm/providers/index.js +2 -8
  40. package/dist/llm/providers/index.js.map +1 -1
  41. package/dist/llm/providers/lookup.js +1 -1
  42. package/dist/llm/providers/lookup.js.map +1 -1
  43. package/dist/models/project.d.ts +59 -15
  44. package/dist/models/project.d.ts.map +1 -1
  45. package/dist/models/project.js +5 -0
  46. package/dist/models/project.js.map +1 -1
  47. package/dist/pipeline/runner.d.ts +2 -0
  48. package/dist/pipeline/runner.d.ts.map +1 -1
  49. package/dist/pipeline/runner.js +3 -0
  50. package/dist/pipeline/runner.js.map +1 -1
  51. package/dist/pipeline/short-fiction-runner.d.ts +91 -0
  52. package/dist/pipeline/short-fiction-runner.d.ts.map +1 -0
  53. package/dist/pipeline/short-fiction-runner.js +535 -0
  54. package/dist/pipeline/short-fiction-runner.js.map +1 -0
  55. package/dist/prompts/index.d.ts +2 -0
  56. package/dist/prompts/index.d.ts.map +1 -0
  57. package/dist/prompts/index.js +2 -0
  58. package/dist/prompts/index.js.map +1 -0
  59. package/dist/prompts/short-fiction.d.ts +52 -0
  60. package/dist/prompts/short-fiction.d.ts.map +1 -0
  61. package/dist/prompts/short-fiction.js +215 -0
  62. package/dist/prompts/short-fiction.js.map +1 -0
  63. package/dist/state/state-bootstrap.d.ts.map +1 -1
  64. package/dist/state/state-bootstrap.js +63 -3
  65. package/dist/state/state-bootstrap.js.map +1 -1
  66. package/dist/utils/context-filter.d.ts +11 -0
  67. package/dist/utils/context-filter.d.ts.map +1 -1
  68. package/dist/utils/context-filter.js +29 -0
  69. package/dist/utils/context-filter.js.map +1 -1
  70. package/package.json +1 -1
@@ -0,0 +1,91 @@
1
+ import type { AgentContext } from "../agents/base.js";
2
+ import { type ShortFictionReference } from "../agents/short-fiction.js";
3
+ import { type CoverProviderPreset } from "../llm/cover-providers.js";
4
+ export interface ShortFictionRunRuntimes {
5
+ readonly planner: AgentContext;
6
+ readonly outlineReview: AgentContext;
7
+ readonly writer: AgentContext;
8
+ readonly draftReview: AgentContext;
9
+ readonly revise: AgentContext;
10
+ readonly package: AgentContext;
11
+ }
12
+ export interface ShortFictionRunOptions {
13
+ readonly projectRoot: string;
14
+ readonly direction: string;
15
+ readonly runtimes: ShortFictionRunRuntimes;
16
+ readonly reference?: ShortFictionReference;
17
+ readonly storyId?: string;
18
+ readonly outDir?: string;
19
+ readonly chapterCount?: number;
20
+ readonly charsPerChapter?: number;
21
+ readonly cover?: boolean;
22
+ readonly coverBaseUrl?: string;
23
+ readonly coverEndpoint?: string;
24
+ readonly coverModel?: string;
25
+ readonly coverSize?: string;
26
+ readonly coverApiKeyEnv?: string;
27
+ readonly onProgress?: (message: string) => void;
28
+ }
29
+ export interface ShortFictionRunResult {
30
+ readonly storyId: string;
31
+ readonly outlinePath: string;
32
+ readonly outlineReviewPath: string;
33
+ readonly draftReviewPath: string;
34
+ readonly finalMarkdownPath: string;
35
+ readonly finalJsonPath: string;
36
+ readonly salesPackagePath: string;
37
+ readonly coverPromptPath: string;
38
+ readonly coverImagePath?: string;
39
+ readonly coverError?: string;
40
+ }
41
+ export interface ShortFictionCoverOptions {
42
+ readonly projectRoot: string;
43
+ readonly title: string;
44
+ readonly intro?: string;
45
+ readonly sellingPoints?: string | ReadonlyArray<string>;
46
+ readonly coverPrompt?: string;
47
+ readonly outputDir?: string;
48
+ readonly coverBaseUrl?: string;
49
+ readonly coverEndpoint?: string;
50
+ readonly coverModel?: string;
51
+ readonly coverSize?: string;
52
+ readonly coverApiKeyEnv?: string;
53
+ }
54
+ export interface ShortFictionCoverResult {
55
+ readonly title: string;
56
+ readonly outputDir: string;
57
+ readonly coverPromptPath: string;
58
+ readonly coverImagePath: string;
59
+ }
60
+ export declare function runShortFictionProduction(options: ShortFictionRunOptions): Promise<ShortFictionRunResult>;
61
+ export declare function generateShortFictionCover(options: ShortFictionCoverOptions): Promise<ShortFictionCoverResult>;
62
+ export interface ShortFictionCoverRequest {
63
+ readonly api: CoverProviderPreset["api"];
64
+ readonly baseUrl: string;
65
+ readonly endpoint?: string;
66
+ readonly model: string;
67
+ readonly apiKey: string;
68
+ }
69
+ export declare function resolveCoverGenerationRequest(input: {
70
+ readonly root: string;
71
+ readonly coverBaseUrl?: string;
72
+ readonly coverEndpoint?: string;
73
+ readonly coverModel?: string;
74
+ readonly coverApiKeyEnv?: string;
75
+ }): Promise<ShortFictionCoverRequest>;
76
+ export declare function extractImagesGenerationImage(payload: unknown): ({
77
+ readonly base64: string;
78
+ readonly extension: "png" | "jpg";
79
+ readonly url?: undefined;
80
+ } | {
81
+ readonly url: string;
82
+ readonly base64?: undefined;
83
+ readonly extension?: undefined;
84
+ }) | undefined;
85
+ export declare function extractResponsesImageBase64(payload: unknown): string | undefined;
86
+ export declare function extractGeminiImageBase64(payload: unknown): {
87
+ readonly base64: string;
88
+ readonly extension: "png" | "jpg";
89
+ } | undefined;
90
+ export declare function resolveCoverApiKey(apiKeyEnv: string): string;
91
+ //# sourceMappingURL=short-fiction-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"short-fiction-runner.d.ts","sourceRoot":"","sources":["../../src/pipeline/short-fiction-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,535 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { SHORT_FICTION_DEFAULT_CHAPTERS, SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER, SHORT_FICTION_MAX_CHAPTERS, SHORT_FICTION_MAX_CHARS_PER_CHAPTER, SHORT_FICTION_MIN_CHAPTERS, SHORT_FICTION_MIN_CHARS_PER_CHAPTER, ShortFictionDraftReviewerAgent, ShortFictionDraftReviserAgent, ShortFictionOutlineAgent, ShortFictionOutlineReviewerAgent, ShortFictionOutlineReviserAgent, ShortFictionPackagingAgent, ShortFictionWriterAgent, renderShortFictionDraftMarkdown, validateShortFictionDraftForFinal, } from "../agents/short-fiction.js";
5
+ import { coverSecretKey, resolveCoverProviderPreset } from "../llm/cover-providers.js";
6
+ import { loadSecrets } from "../llm/secrets.js";
7
+ import { safeChildPath } from "../utils/path-safety.js";
8
+ export async function runShortFictionProduction(options) {
9
+ const root = options.projectRoot;
10
+ const chapterCount = boundedInteger(options.chapterCount, SHORT_FICTION_DEFAULT_CHAPTERS, "chapterCount", SHORT_FICTION_MIN_CHAPTERS, SHORT_FICTION_MAX_CHAPTERS);
11
+ const charsPerChapter = boundedInteger(options.charsPerChapter, SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER, "charsPerChapter", SHORT_FICTION_MIN_CHARS_PER_CHAPTER, SHORT_FICTION_MAX_CHARS_PER_CHAPTER);
12
+ options.onProgress?.("Creating short fiction outline...");
13
+ const outlineAgent = new ShortFictionOutlineAgent(options.runtimes.planner);
14
+ const outlineV1 = await outlineAgent.createOutline({
15
+ direction: options.direction,
16
+ chapterCount,
17
+ charsPerChapter,
18
+ reference: options.reference,
19
+ });
20
+ const storyId = safeSegment(options.storyId || slugify(outlineV1.storyTitle || options.direction));
21
+ const baseDir = join(normalizeOutputDir(options.outDir ?? "shorts"), storyId);
22
+ await writeText(root, join(baseDir, "outline", "v001.md"), outlineV1.rawContent);
23
+ options.onProgress?.("Reviewing outline...");
24
+ const outlineReviewer = new ShortFictionOutlineReviewerAgent(options.runtimes.outlineReview);
25
+ const outlineReview = await outlineReviewer.reviewOutline({
26
+ direction: options.direction,
27
+ outline: outlineV1,
28
+ reference: options.reference,
29
+ });
30
+ await writeText(root, join(baseDir, "reviews", "outline-v001.md"), outlineReview);
31
+ options.onProgress?.("Revising outline once...");
32
+ const outlineReviser = new ShortFictionOutlineReviserAgent(options.runtimes.planner);
33
+ const outlineV2 = await outlineReviser.reviseOutline({
34
+ direction: options.direction,
35
+ outline: outlineV1,
36
+ review: outlineReview,
37
+ reference: options.reference,
38
+ chapterCount,
39
+ charsPerChapter,
40
+ });
41
+ await writeText(root, join(baseDir, "outline", "v002.md"), outlineV2.rawContent);
42
+ options.onProgress?.("Writing full short fiction draft...");
43
+ const writer = new ShortFictionWriterAgent(options.runtimes.writer);
44
+ const draftV1 = await writer.writeDraft({
45
+ direction: options.direction,
46
+ outlineMarkdown: outlineV2.rawContent,
47
+ chapterCount,
48
+ charsPerChapter,
49
+ });
50
+ await writeDraftArtifacts(root, baseDir, "v001", draftV1);
51
+ options.onProgress?.("Reviewing full draft...");
52
+ const draftReviewer = new ShortFictionDraftReviewerAgent(options.runtimes.draftReview);
53
+ const draftReview = await draftReviewer.reviewDraft({
54
+ direction: options.direction,
55
+ outlineMarkdown: outlineV2.rawContent,
56
+ draft: draftV1,
57
+ chapterCount,
58
+ charsPerChapter,
59
+ });
60
+ await writeText(root, join(baseDir, "reviews", "draft-v001.md"), draftReview);
61
+ options.onProgress?.("Revising full draft once...");
62
+ const reviser = new ShortFictionDraftReviserAgent(options.runtimes.revise);
63
+ const draftV2 = await reviser.reviseDraft({
64
+ direction: options.direction,
65
+ outlineMarkdown: outlineV2.rawContent,
66
+ draft: draftV1,
67
+ review: draftReview,
68
+ chapterCount,
69
+ charsPerChapter,
70
+ });
71
+ validateShortFictionDraftForFinal(draftV2, { expectedChapters: chapterCount });
72
+ await writeDraftArtifacts(root, baseDir, "v002", draftV2);
73
+ await writeFinalArtifacts(root, baseDir, draftV2);
74
+ options.onProgress?.("Generating synopsis and cover prompt...");
75
+ const packager = new ShortFictionPackagingAgent(options.runtimes.package);
76
+ const salesPackage = await packager.generatePackage({
77
+ direction: options.direction,
78
+ outlineMarkdown: outlineV2.rawContent,
79
+ draft: draftV2,
80
+ });
81
+ await writePackageArtifacts(root, baseDir, salesPackage);
82
+ const coverArtifacts = options.cover === false
83
+ ? { coverError: "disabled" }
84
+ : await generateCoverArtifact({
85
+ root,
86
+ baseDir,
87
+ salesPackage,
88
+ coverBaseUrl: options.coverBaseUrl,
89
+ coverEndpoint: options.coverEndpoint,
90
+ coverModel: options.coverModel,
91
+ coverSize: options.coverSize,
92
+ coverApiKeyEnv: options.coverApiKeyEnv,
93
+ }).catch((error) => ({ coverError: String(error) }));
94
+ return {
95
+ storyId,
96
+ outlinePath: join(baseDir, "outline", "v002.md"),
97
+ outlineReviewPath: join(baseDir, "reviews", "outline-v001.md"),
98
+ draftReviewPath: join(baseDir, "reviews", "draft-v001.md"),
99
+ finalMarkdownPath: join(baseDir, "final", "full.md"),
100
+ finalJsonPath: join(baseDir, "final", "short-story.json"),
101
+ salesPackagePath: join(baseDir, "final", "sales-package.md"),
102
+ coverPromptPath: join(baseDir, "final", "cover-prompt.md"),
103
+ coverImagePath: coverArtifacts.coverImagePath,
104
+ coverError: coverArtifacts.coverError,
105
+ };
106
+ }
107
+ export async function generateShortFictionCover(options) {
108
+ const title = options.title.trim();
109
+ if (!title) {
110
+ throw new Error("title is required for cover generation.");
111
+ }
112
+ const outputDir = normalizeOutputDir(options.outputDir ?? join("covers", safeSegment(title)));
113
+ const salesPackage = {
114
+ title,
115
+ intro: options.intro?.trim() ?? "",
116
+ sellingPoints: normalizeSellingPoints(options.sellingPoints),
117
+ coverPrompt: options.coverPrompt?.trim() ?? "",
118
+ rawContent: "",
119
+ };
120
+ const promptPath = join(outputDir, "cover-prompt.md");
121
+ await writeText(options.projectRoot, promptPath, buildCoverImagePrompt(salesPackage));
122
+ const artifact = await generateCoverImageArtifact({
123
+ root: options.projectRoot,
124
+ outputDir,
125
+ salesPackage,
126
+ coverBaseUrl: options.coverBaseUrl,
127
+ coverEndpoint: options.coverEndpoint,
128
+ coverModel: options.coverModel,
129
+ coverSize: options.coverSize,
130
+ coverApiKeyEnv: options.coverApiKeyEnv,
131
+ });
132
+ return {
133
+ title,
134
+ outputDir,
135
+ coverPromptPath: promptPath,
136
+ coverImagePath: artifact.coverImagePath,
137
+ };
138
+ }
139
+ async function writeDraftArtifacts(root, baseDir, version, draft) {
140
+ const draftDir = join(baseDir, "drafts", version);
141
+ await writeText(root, join(draftDir, "full.md"), renderShortFictionDraftMarkdown(draft));
142
+ await writeJson(root, join(draftDir, "draft.json"), draft);
143
+ await Promise.all(draft.chapters.map((chapter) => writeText(root, join(draftDir, "chapters", `${String(chapter.number).padStart(4, "0")}.md`), [
144
+ `# 第${chapter.number}章 ${chapter.title}`,
145
+ "",
146
+ chapter.content,
147
+ ].join("\n"))));
148
+ }
149
+ async function writeFinalArtifacts(root, baseDir, draft) {
150
+ const finalDir = join(baseDir, "final");
151
+ const markdown = renderShortFictionDraftMarkdown(draft);
152
+ await writeText(root, join(finalDir, "full.md"), markdown);
153
+ await writeText(root, join(finalDir, `${safeFileName(draft.storyTitle)}.md`), markdown);
154
+ await writeJson(root, join(finalDir, "short-story.json"), draft);
155
+ await Promise.all(draft.chapters.map((chapter) => writeText(root, join(finalDir, "chapters", `${String(chapter.number).padStart(4, "0")}.md`), [
156
+ `# 第${chapter.number}章 ${chapter.title}`,
157
+ "",
158
+ chapter.content,
159
+ ].join("\n"))));
160
+ }
161
+ async function writePackageArtifacts(root, baseDir, salesPackage) {
162
+ const finalDir = join(baseDir, "final");
163
+ await writeJson(root, join(finalDir, "sales-package.json"), salesPackage);
164
+ await writeText(root, join(finalDir, "sales-package.md"), [
165
+ `# ${salesPackage.title}`,
166
+ "",
167
+ "## 简介",
168
+ "",
169
+ salesPackage.intro,
170
+ "",
171
+ "## 卖点",
172
+ "",
173
+ ...salesPackage.sellingPoints.map((point) => `- ${point}`),
174
+ "",
175
+ "## 封面提示词",
176
+ "",
177
+ salesPackage.coverPrompt,
178
+ ].join("\n"));
179
+ await writeText(root, join(finalDir, "cover-prompt.md"), salesPackage.coverPrompt || "(empty)");
180
+ }
181
+ async function generateCoverArtifact(input) {
182
+ return generateCoverImageArtifact({
183
+ ...input,
184
+ outputDir: join(input.baseDir, "final"),
185
+ });
186
+ }
187
+ async function generateCoverImageArtifact(input) {
188
+ const request = await resolveCoverGenerationRequest({
189
+ root: input.root,
190
+ coverBaseUrl: input.coverBaseUrl,
191
+ coverEndpoint: input.coverEndpoint,
192
+ coverModel: input.coverModel,
193
+ coverApiKeyEnv: input.coverApiKeyEnv,
194
+ });
195
+ const size = input.coverSize || process.env.INKOS_COVER_SIZE || "1024x1360";
196
+ if (request.api === "gemini") {
197
+ const prompt = buildCoverImagePrompt(input.salesPackage);
198
+ const payload = await generateGeminiCover(request, prompt);
199
+ const coverPath = join(input.outputDir, payload.extension === "jpg" ? "cover.jpg" : "cover.png");
200
+ await writeBinary(input.root, coverPath, Buffer.from(payload.base64, "base64"));
201
+ return { coverImagePath: coverPath };
202
+ }
203
+ if (request.api === "images") {
204
+ const prompt = buildCoverImagePrompt(input.salesPackage);
205
+ const payload = await generateImagesCover(request, prompt, size);
206
+ const coverPath = join(input.outputDir, payload.extension === "jpg" ? "cover.jpg" : "cover.png");
207
+ await writeBinary(input.root, coverPath, payload.buffer);
208
+ return { coverImagePath: coverPath };
209
+ }
210
+ const endpoint = request.endpoint ?? `${request.baseUrl.replace(/\/+$/u, "")}/responses`;
211
+ const response = await fetch(endpoint, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ Authorization: `Bearer ${request.apiKey}`,
216
+ },
217
+ body: JSON.stringify({
218
+ model: request.model,
219
+ input: buildCoverImagePrompt(input.salesPackage),
220
+ tools: [{ type: "image_generation", size }],
221
+ }),
222
+ });
223
+ const text = await response.text();
224
+ if (!response.ok) {
225
+ throw new Error(`cover generation failed: HTTP ${response.status} ${text.slice(0, 500)}`);
226
+ }
227
+ let payload;
228
+ try {
229
+ payload = JSON.parse(text);
230
+ }
231
+ catch (error) {
232
+ throw new Error(`cover generation returned non-JSON response: ${String(error)}`);
233
+ }
234
+ const imageBase64 = extractResponsesImageBase64(payload);
235
+ if (!imageBase64) {
236
+ throw new Error("cover generation response did not include image_generation_call result.");
237
+ }
238
+ const coverPath = join(input.outputDir, "cover.png");
239
+ await writeBinary(input.root, coverPath, Buffer.from(imageBase64, "base64"));
240
+ return { coverImagePath: coverPath };
241
+ }
242
+ export async function resolveCoverGenerationRequest(input) {
243
+ if (input.coverEndpoint || input.coverBaseUrl || process.env.INKOS_COVER_ENDPOINT || process.env.INKOS_COVER_BASE_URL) {
244
+ const endpoint = resolveCoverEndpoint(input.coverEndpoint, input.coverBaseUrl);
245
+ const baseUrl = input.coverBaseUrl || process.env.INKOS_COVER_BASE_URL || endpoint
246
+ .replace(/\/responses\/?$/u, "")
247
+ .replace(/\/images\/generations\/?$/u, "");
248
+ return {
249
+ api: endpoint.includes("/responses") ? "responses" : "images",
250
+ baseUrl,
251
+ endpoint,
252
+ model: input.coverModel || process.env.INKOS_COVER_MODEL || "gpt-image-2",
253
+ apiKey: resolveCoverApiKey(input.coverApiKeyEnv || "INKOS_COVER_API_KEY"),
254
+ };
255
+ }
256
+ const projectCover = await readProjectCoverConfig(input.root);
257
+ if (!projectCover) {
258
+ throw new Error("cover endpoint is required. Configure cover generation in Studio or set INKOS_COVER_BASE_URL.");
259
+ }
260
+ const preset = resolveCoverProviderPreset(projectCover.service);
261
+ if (!preset) {
262
+ throw new Error(`Unsupported cover service: ${projectCover.service}`);
263
+ }
264
+ const apiKey = await resolveProjectCoverApiKey(input.root, projectCover.service);
265
+ if (!apiKey) {
266
+ throw new Error(`Cover API key is required. Configure a cover key for ${preset.label}.`);
267
+ }
268
+ return {
269
+ api: preset.api,
270
+ baseUrl: preset.baseUrl,
271
+ model: input.coverModel || projectCover.model || preset.defaultModel,
272
+ apiKey,
273
+ };
274
+ }
275
+ async function readProjectCoverConfig(root) {
276
+ try {
277
+ const raw = await readFile(join(root, "inkos.json"), "utf-8");
278
+ const parsed = JSON.parse(raw);
279
+ const service = typeof parsed.llm?.cover?.service === "string" ? parsed.llm.cover.service : "";
280
+ if (!service)
281
+ return undefined;
282
+ return {
283
+ service,
284
+ ...(typeof parsed.llm?.cover?.model === "string" && parsed.llm.cover.model.trim()
285
+ ? { model: parsed.llm.cover.model.trim() }
286
+ : {}),
287
+ };
288
+ }
289
+ catch {
290
+ return undefined;
291
+ }
292
+ }
293
+ async function resolveProjectCoverApiKey(root, service) {
294
+ const secrets = await loadSecrets(root);
295
+ return secrets.services[coverSecretKey(service)]?.apiKey
296
+ || secrets.services[service]?.apiKey
297
+ || process.env[`${service.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()}_API_KEY`]
298
+ || "";
299
+ }
300
+ async function generateImagesCover(request, prompt, size) {
301
+ const endpoint = request.endpoint ?? `${request.baseUrl.replace(/\/+$/u, "")}/images/generations`;
302
+ const response = await fetch(endpoint, {
303
+ method: "POST",
304
+ headers: {
305
+ "Content-Type": "application/json",
306
+ Authorization: `Bearer ${request.apiKey}`,
307
+ },
308
+ body: JSON.stringify({
309
+ model: request.model,
310
+ prompt,
311
+ n: 1,
312
+ size,
313
+ }),
314
+ });
315
+ const text = await response.text();
316
+ if (!response.ok) {
317
+ throw new Error(`cover generation failed: HTTP ${response.status} ${text.slice(0, 500)}`);
318
+ }
319
+ let payload;
320
+ try {
321
+ payload = JSON.parse(text);
322
+ }
323
+ catch (error) {
324
+ throw new Error(`cover generation returned non-JSON response: ${String(error)}`);
325
+ }
326
+ const image = extractImagesGenerationImage(payload);
327
+ if (image?.base64) {
328
+ return {
329
+ buffer: Buffer.from(image.base64, "base64"),
330
+ extension: image.extension,
331
+ };
332
+ }
333
+ if (image?.url) {
334
+ return downloadGeneratedCoverImage(image.url, request.apiKey);
335
+ }
336
+ throw new Error("cover generation response did not include image URL or base64 data.");
337
+ }
338
+ export function extractImagesGenerationImage(payload) {
339
+ const data = payload.data;
340
+ if (!Array.isArray(data))
341
+ return undefined;
342
+ for (const item of data) {
343
+ const record = item;
344
+ if (typeof record.b64_json === "string" && record.b64_json.trim()) {
345
+ return { base64: record.b64_json.trim(), extension: "png" };
346
+ }
347
+ if (typeof record.url === "string" && record.url.trim()) {
348
+ return { url: record.url.trim() };
349
+ }
350
+ }
351
+ return undefined;
352
+ }
353
+ async function downloadGeneratedCoverImage(url, apiKey) {
354
+ const response = await fetch(url);
355
+ const fallbackResponse = response.status === 401 || response.status === 403
356
+ ? await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } })
357
+ : response;
358
+ if (!fallbackResponse.ok) {
359
+ const text = await fallbackResponse.text();
360
+ throw new Error(`cover image download failed: HTTP ${fallbackResponse.status} ${text.slice(0, 300)}`);
361
+ }
362
+ const contentType = fallbackResponse.headers.get("content-type") ?? "";
363
+ const buffer = Buffer.from(await fallbackResponse.arrayBuffer());
364
+ return {
365
+ buffer,
366
+ extension: coverImageExtension(contentType, url),
367
+ };
368
+ }
369
+ function coverImageExtension(contentType, url) {
370
+ const normalized = `${contentType} ${url}`.toLowerCase();
371
+ return normalized.includes("jpeg") || normalized.includes(".jpg") || normalized.includes(".jpeg") ? "jpg" : "png";
372
+ }
373
+ async function generateGeminiCover(request, prompt) {
374
+ const endpoint = `${request.baseUrl.replace(/\/+$/u, "")}/models/${encodeURIComponent(request.model)}:generateContent?key=${encodeURIComponent(request.apiKey)}`;
375
+ const response = await fetch(endpoint, {
376
+ method: "POST",
377
+ headers: { "Content-Type": "application/json" },
378
+ body: JSON.stringify({
379
+ contents: [{ role: "user", parts: [{ text: prompt }] }],
380
+ generationConfig: { responseModalities: ["IMAGE", "TEXT"] },
381
+ }),
382
+ });
383
+ const text = await response.text();
384
+ if (!response.ok) {
385
+ throw new Error(`cover generation failed: HTTP ${response.status} ${text.slice(0, 500)}`);
386
+ }
387
+ let payload;
388
+ try {
389
+ payload = JSON.parse(text);
390
+ }
391
+ catch (error) {
392
+ throw new Error(`cover generation returned non-JSON response: ${String(error)}`);
393
+ }
394
+ const image = extractGeminiImageBase64(payload);
395
+ if (!image) {
396
+ throw new Error("cover generation response did not include Gemini inline image data.");
397
+ }
398
+ return image;
399
+ }
400
+ export function extractResponsesImageBase64(payload) {
401
+ const output = payload.output;
402
+ if (!Array.isArray(output))
403
+ return undefined;
404
+ for (const item of output) {
405
+ const record = item;
406
+ if (record.type === "image_generation_call" && typeof record.result === "string" && record.result.trim()) {
407
+ return record.result.trim();
408
+ }
409
+ if (Array.isArray(record.content)) {
410
+ for (const contentItem of record.content) {
411
+ const contentRecord = contentItem;
412
+ if (typeof contentRecord.result === "string" && contentRecord.result.trim())
413
+ return contentRecord.result.trim();
414
+ if (typeof contentRecord.image_base64 === "string" && contentRecord.image_base64.trim())
415
+ return contentRecord.image_base64.trim();
416
+ }
417
+ }
418
+ }
419
+ return undefined;
420
+ }
421
+ export function extractGeminiImageBase64(payload) {
422
+ const candidates = payload.candidates;
423
+ if (!Array.isArray(candidates))
424
+ return undefined;
425
+ for (const candidate of candidates) {
426
+ const parts = candidate.content?.parts;
427
+ if (!Array.isArray(parts))
428
+ continue;
429
+ for (const part of parts) {
430
+ const inlineData = part.inlineData
431
+ ?? part.inline_data;
432
+ const record = inlineData;
433
+ if (typeof record?.data !== "string" || !record.data.trim())
434
+ continue;
435
+ const mimeType = String(record.mimeType ?? record.mime_type ?? "image/png").toLowerCase();
436
+ return {
437
+ base64: record.data.trim(),
438
+ extension: mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png",
439
+ };
440
+ }
441
+ }
442
+ return undefined;
443
+ }
444
+ export function resolveCoverApiKey(apiKeyEnv) {
445
+ const apiKey = process.env[apiKeyEnv];
446
+ if (!apiKey) {
447
+ throw new Error(`Cover API key is required. Set ${apiKeyEnv} or pass coverApiKeyEnv.`);
448
+ }
449
+ return apiKey;
450
+ }
451
+ function resolveCoverEndpoint(coverEndpoint, coverBaseUrl) {
452
+ const endpoint = coverEndpoint || process.env.INKOS_COVER_ENDPOINT;
453
+ if (endpoint)
454
+ return endpoint;
455
+ const baseUrl = coverBaseUrl || process.env.INKOS_COVER_BASE_URL;
456
+ if (!baseUrl) {
457
+ throw new Error("cover endpoint is required. Set INKOS_COVER_BASE_URL or disable cover generation.");
458
+ }
459
+ return `${baseUrl.replace(/\/+$/u, "")}/images/generations`;
460
+ }
461
+ function buildCoverImagePrompt(salesPackage) {
462
+ return [
463
+ "为中文商业短篇小说生成手机端平台书封,3:4竖图。",
464
+ `主标题:${salesPackage.title}`,
465
+ salesPackage.intro ? `简介:${salesPackage.intro}` : "",
466
+ salesPackage.sellingPoints.length > 0 ? `卖点:${salesPackage.sellingPoints.join(";")}` : "",
467
+ salesPackage.coverPrompt ? `包装提示:${salesPackage.coverPrompt}` : "",
468
+ "",
469
+ "封面方向:平台短篇书封,不是电影海报。标题字要成为主视觉,预留两到四行大字排版区;人物近景或半身,表情有冷笑、震惊、崩溃、压迫或反杀感;道具少而大,一眼能看出冲突。",
470
+ "颜色高对比、高饱和,适合手机列表缩略图。避免写实会议摄影、横版视频缩略图、杂志大片、小清新细字和长段文字。",
471
+ "如果模型文字不稳定,优先生成明确标题留白/字块/排版空间,不要把大量乱码文字铺满画面。",
472
+ ].filter(Boolean).join("\n");
473
+ }
474
+ function normalizeSellingPoints(value) {
475
+ if (typeof value === "string" || value === undefined) {
476
+ return (value ?? "")
477
+ .split(/[;;\n]/u)
478
+ .map((point) => point.trim())
479
+ .filter(Boolean);
480
+ }
481
+ return value.map((point) => point.trim()).filter(Boolean);
482
+ }
483
+ async function writeBinary(root, path, value) {
484
+ const resolved = safeChildPath(root, path);
485
+ await mkdir(dirname(resolved), { recursive: true });
486
+ await writeFile(resolved, value);
487
+ }
488
+ async function writeJson(root, path, value) {
489
+ await writeText(root, path, JSON.stringify(value, null, 2));
490
+ }
491
+ async function writeText(root, path, value) {
492
+ const resolved = safeChildPath(root, path);
493
+ await mkdir(dirname(resolved), { recursive: true });
494
+ await writeFile(resolved, `${value.trimEnd()}\n`, "utf-8");
495
+ }
496
+ function normalizeOutputDir(value) {
497
+ const trimmed = value.trim() || "shorts";
498
+ safeChildPath("/", trimmed);
499
+ return trimmed.replace(/^\/+/u, "").replace(/\/+$/u, "") || "shorts";
500
+ }
501
+ function boundedInteger(value, fallback, name, min, max) {
502
+ const parsed = value ?? fallback;
503
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
504
+ throw new Error(`${name} must be an integer between ${min} and ${max}.`);
505
+ }
506
+ return parsed;
507
+ }
508
+ function slugify(value) {
509
+ const slug = value
510
+ .toLowerCase()
511
+ .replace(/['"]/g, "")
512
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
513
+ .replace(/^-+|-+$/g, "")
514
+ .slice(0, 60);
515
+ return slug || `short-${Date.now()}`;
516
+ }
517
+ function safeSegment(value) {
518
+ const cleaned = value
519
+ .replace(/[\\/:\0*?"<>|]/g, "-")
520
+ .replace(/\s+/g, "-")
521
+ .replace(/^-+|-+$/g, "")
522
+ .slice(0, 80);
523
+ if (!cleaned || cleaned === "." || cleaned === "..")
524
+ return `short-${Date.now()}`;
525
+ return cleaned;
526
+ }
527
+ function safeFileName(value) {
528
+ const cleaned = value
529
+ .replace(/[\\/:\0*?"<>|]/g, "_")
530
+ .replace(/\s+/g, " ")
531
+ .trim()
532
+ .slice(0, 80);
533
+ return cleaned || "short-fiction";
534
+ }
535
+ //# sourceMappingURL=short-fiction-runner.js.map