ima2-gen 1.1.20 → 1.1.22

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 (239) hide show
  1. package/README.md +42 -26
  2. package/bin/commands/capabilities.js +2 -2
  3. package/bin/commands/defaults.js +2 -2
  4. package/bin/commands/doctor.js +3 -3
  5. package/bin/commands/edit.js +1 -1
  6. package/bin/commands/gen.js +1 -1
  7. package/bin/commands/grok.js +16 -11
  8. package/bin/commands/multimode.js +1 -1
  9. package/bin/commands/observability.js +2 -2
  10. package/bin/commands/video.js +335 -13
  11. package/bin/ima2.js +23 -13
  12. package/bin/lib/error-hints.js +2 -2
  13. package/bin/lib/platform.js +34 -5
  14. package/docs/API.md +112 -3
  15. package/docs/CLI.md +61 -7
  16. package/docs/FAQ.ko.md +15 -20
  17. package/docs/FAQ.md +14 -19
  18. package/docs/NPX_QUICKSTART.md +40 -0
  19. package/docs/PROMPT_STUDIO.ko.md +1 -1
  20. package/docs/PROMPT_STUDIO.md +1 -1
  21. package/docs/README.ja.md +6 -16
  22. package/docs/README.ko.md +35 -14
  23. package/docs/README.zh-CN.md +7 -17
  24. package/docs/migration/runtime-test-inventory.md +8 -1
  25. package/lib/agentQueueWorker.js +6 -0
  26. package/lib/agentRuntime.js +20 -5
  27. package/lib/atomicWrite.js +14 -0
  28. package/lib/capabilities.js +1 -1
  29. package/lib/generationErrors.js +1 -1
  30. package/lib/grokProxyLauncher.js +31 -6
  31. package/lib/grokVideoAdapter.js +18 -89
  32. package/lib/grokVideoCanvas.js +25 -0
  33. package/lib/grokVideoDownload.js +58 -0
  34. package/lib/grokVideoPlannerPrompt.js +64 -0
  35. package/lib/historyList.js +7 -1
  36. package/lib/inflight.js +1 -1
  37. package/lib/oauthLauncher.js +26 -6
  38. package/lib/videoContinuity.js +149 -0
  39. package/lib/videoFrameExtract.js +80 -0
  40. package/node_modules/progrok/dist/index.js +187 -88
  41. package/node_modules/progrok/dist/index.js.map +1 -1
  42. package/node_modules/progrok/package.json +1 -1
  43. package/node_modules/progrok/skills/progrok/SKILL.md +33 -4
  44. package/package.json +6 -8
  45. package/routes/edit.js +2 -1
  46. package/routes/generate.js +4 -3
  47. package/routes/health.js +4 -3
  48. package/routes/index.js +4 -0
  49. package/routes/multimode.js +2 -1
  50. package/routes/quota.js +66 -0
  51. package/routes/video.js +80 -16
  52. package/routes/videoExtended.js +293 -0
  53. package/server.js +35 -4
  54. package/skills/ima2/SKILL.md +320 -7
  55. package/ui/dist/.vite/manifest.json +12 -12
  56. package/ui/dist/assets/{AgentWorkspace-DS8uvoLI.js → AgentWorkspace-COxQ5TjU.js} +2 -2
  57. package/ui/dist/assets/{CardNewsWorkspace-CYxMsE67.js → CardNewsWorkspace-B0OkcuVz.js} +1 -1
  58. package/ui/dist/assets/{NodeCanvas-DccIc347.js → NodeCanvas-BSsclEBh.js} +1 -1
  59. package/ui/dist/assets/{PromptBuilderPanel-BvxxwSJp.js → PromptBuilderPanel-DpC9A5Rz.js} +1 -1
  60. package/ui/dist/assets/{PromptImportDialog-u1_BFDRd.js → PromptImportDialog-CVwT0rLd.js} +2 -2
  61. package/ui/dist/assets/{PromptImportDiscoverySection-C5uvkVSz.js → PromptImportDiscoverySection-BDCkRCRs.js} +1 -1
  62. package/ui/dist/assets/{PromptImportFolderSection-D3E_O1SD.js → PromptImportFolderSection-QoKbZD83.js} +1 -1
  63. package/ui/dist/assets/{PromptLibraryPanel-4gyf9CB9.js → PromptLibraryPanel-BhFgeKnY.js} +2 -2
  64. package/ui/dist/assets/SettingsWorkspace-CfjrlH5R.js +1 -0
  65. package/ui/dist/assets/index-C-mur7pa.css +1 -0
  66. package/ui/dist/assets/index-CCP5nUOj.js +42 -0
  67. package/ui/dist/assets/{index-DoKtXbod.js → index-Cxhzi3bs.js} +1 -1
  68. package/ui/dist/index.html +2 -2
  69. package/vendor/progrok-0.2.0.tgz +0 -0
  70. package/bin/commands/annotate.ts +0 -119
  71. package/bin/commands/cancel.ts +0 -48
  72. package/bin/commands/canvas-versions.ts +0 -80
  73. package/bin/commands/capabilities.ts +0 -110
  74. package/bin/commands/cardnews.ts +0 -249
  75. package/bin/commands/comfy.ts +0 -54
  76. package/bin/commands/config.ts +0 -186
  77. package/bin/commands/defaults.ts +0 -192
  78. package/bin/commands/doctor.ts +0 -202
  79. package/bin/commands/edit.ts +0 -150
  80. package/bin/commands/gen.ts +0 -214
  81. package/bin/commands/grok.ts +0 -85
  82. package/bin/commands/history.ts +0 -146
  83. package/bin/commands/ls.ts +0 -64
  84. package/bin/commands/metadata.ts +0 -39
  85. package/bin/commands/multimode.ts +0 -196
  86. package/bin/commands/node.ts +0 -166
  87. package/bin/commands/observability.ts +0 -176
  88. package/bin/commands/ping.ts +0 -31
  89. package/bin/commands/prompt-sub/build.ts +0 -101
  90. package/bin/commands/prompt.ts +0 -492
  91. package/bin/commands/ps.ts +0 -81
  92. package/bin/commands/session.ts +0 -266
  93. package/bin/commands/show.ts +0 -72
  94. package/bin/commands/skill.ts +0 -70
  95. package/bin/commands/video.ts +0 -205
  96. package/bin/ima2.ts +0 -430
  97. package/bin/lib/args.ts +0 -92
  98. package/bin/lib/browser-id.ts +0 -16
  99. package/bin/lib/client.ts +0 -122
  100. package/bin/lib/config-store.ts +0 -120
  101. package/bin/lib/destructive-confirm.ts +0 -19
  102. package/bin/lib/doctor-checks.ts +0 -91
  103. package/bin/lib/error-hints.ts +0 -23
  104. package/bin/lib/files.ts +0 -39
  105. package/bin/lib/output.ts +0 -73
  106. package/bin/lib/platform.ts +0 -99
  107. package/bin/lib/recover-output.ts +0 -139
  108. package/bin/lib/sse.ts +0 -73
  109. package/bin/lib/star-prompt.ts +0 -97
  110. package/bin/lib/storage-doctor.ts +0 -39
  111. package/bin/lib/ui-build.ts +0 -85
  112. package/config.ts +0 -354
  113. package/lib/agentCommandParser.ts +0 -69
  114. package/lib/agentGenerationPlanner.ts +0 -273
  115. package/lib/agentQuestionResponder.ts +0 -266
  116. package/lib/agentQueueStore.ts +0 -270
  117. package/lib/agentQueueWorker.ts +0 -89
  118. package/lib/agentRuntime.ts +0 -592
  119. package/lib/agentSettings.ts +0 -72
  120. package/lib/agentStore.ts +0 -422
  121. package/lib/agentStoreRows.ts +0 -136
  122. package/lib/agentTypes.ts +0 -154
  123. package/lib/apiCachePolicy.ts +0 -11
  124. package/lib/assetLifecycle.ts +0 -146
  125. package/lib/canvasVersionStore.ts +0 -223
  126. package/lib/capabilities.ts +0 -126
  127. package/lib/cardNewsGenerator.ts +0 -271
  128. package/lib/cardNewsJobStore.ts +0 -142
  129. package/lib/cardNewsManifestStore.ts +0 -154
  130. package/lib/cardNewsPlanner.ts +0 -236
  131. package/lib/cardNewsPlannerClient.ts +0 -155
  132. package/lib/cardNewsPlannerPrompt.ts +0 -62
  133. package/lib/cardNewsPlannerSchema.ts +0 -321
  134. package/lib/cardNewsRoleTemplateStore.ts +0 -47
  135. package/lib/cardNewsTemplateStore.ts +0 -252
  136. package/lib/codexDetect.ts +0 -71
  137. package/lib/comfyBridge.ts +0 -235
  138. package/lib/composerSnapshot.ts +0 -33
  139. package/lib/configKeys.ts +0 -62
  140. package/lib/db.ts +0 -295
  141. package/lib/errInfo.ts +0 -43
  142. package/lib/errorClassify.ts +0 -100
  143. package/lib/generationCancel.ts +0 -28
  144. package/lib/generationErrors.ts +0 -238
  145. package/lib/grokImageAdapter.ts +0 -513
  146. package/lib/grokMultimodeAdapter.ts +0 -84
  147. package/lib/grokProxyLauncher.ts +0 -129
  148. package/lib/grokRuntime.ts +0 -23
  149. package/lib/grokSizeMapper.ts +0 -71
  150. package/lib/grokVideoAdapter.ts +0 -519
  151. package/lib/historyIndex.ts +0 -51
  152. package/lib/historyList.ts +0 -177
  153. package/lib/imageMetadata.ts +0 -113
  154. package/lib/imageMetadataStore.ts +0 -67
  155. package/lib/imageModels.ts +0 -165
  156. package/lib/inflight.ts +0 -281
  157. package/lib/localImportStore.ts +0 -114
  158. package/lib/logger.ts +0 -161
  159. package/lib/nodeStore.ts +0 -91
  160. package/lib/oauthLauncher.ts +0 -78
  161. package/lib/oauthNormalize.ts +0 -30
  162. package/lib/oauthProxy/errors.ts +0 -128
  163. package/lib/oauthProxy/generators.ts +0 -494
  164. package/lib/oauthProxy/index.ts +0 -28
  165. package/lib/oauthProxy/prompts.ts +0 -123
  166. package/lib/oauthProxy/references.ts +0 -45
  167. package/lib/oauthProxy/runtime.ts +0 -115
  168. package/lib/oauthProxy/streams.ts +0 -232
  169. package/lib/oauthProxy/types.ts +0 -9
  170. package/lib/oauthProxy.ts +0 -3
  171. package/lib/openDirectory.ts +0 -47
  172. package/lib/pngInfo.ts +0 -26
  173. package/lib/promptBuilder/attachments.ts +0 -74
  174. package/lib/promptBuilder/client.ts +0 -130
  175. package/lib/promptBuilder/constants.ts +0 -9
  176. package/lib/promptBuilder/context.ts +0 -36
  177. package/lib/promptBuilder/errors.ts +0 -12
  178. package/lib/promptBuilder/requestSchema.ts +0 -56
  179. package/lib/promptBuilder/responseParser.ts +0 -219
  180. package/lib/promptBuilder/systemPrompt.ts +0 -135
  181. package/lib/promptBuilder/transport.ts +0 -94
  182. package/lib/promptBuilder/types.ts +0 -109
  183. package/lib/promptImport/curatedSources.ts +0 -141
  184. package/lib/promptImport/discoveryRegistry.ts +0 -329
  185. package/lib/promptImport/errors.ts +0 -18
  186. package/lib/promptImport/githubDiscovery.ts +0 -309
  187. package/lib/promptImport/githubFolder.ts +0 -397
  188. package/lib/promptImport/githubSource.ts +0 -257
  189. package/lib/promptImport/gptImageHints.ts +0 -70
  190. package/lib/promptImport/parsePromptCandidates.ts +0 -179
  191. package/lib/promptImport/promptIndex.ts +0 -326
  192. package/lib/promptImport/rankPromptCandidates.ts +0 -65
  193. package/lib/promptImport/types.ts +0 -103
  194. package/lib/promptSafetyPolicy.ts +0 -5
  195. package/lib/providerOptions.ts +0 -56
  196. package/lib/referenceImageCompress.ts +0 -84
  197. package/lib/refs.ts +0 -133
  198. package/lib/requestLogger.ts +0 -49
  199. package/lib/responsesDoctor.ts +0 -456
  200. package/lib/responsesErrors.ts +0 -83
  201. package/lib/responsesFallback.ts +0 -114
  202. package/lib/responsesImageAdapter.ts +0 -466
  203. package/lib/responsesParse.ts +0 -452
  204. package/lib/responsesTools.ts +0 -28
  205. package/lib/runtimeContext.ts +0 -146
  206. package/lib/runtimePorts.ts +0 -105
  207. package/lib/sessionStore.ts +0 -308
  208. package/lib/storageMigration.ts +0 -310
  209. package/lib/styleSheet.ts +0 -139
  210. package/lib/systemTrash.ts +0 -20
  211. package/lib/videoSeriesChain.ts +0 -29
  212. package/lib/visibleTextLanguagePolicy.ts +0 -7
  213. package/routes/agent.ts +0 -308
  214. package/routes/annotations.ts +0 -118
  215. package/routes/canvasVersions.ts +0 -69
  216. package/routes/capabilities.ts +0 -18
  217. package/routes/cardNews.ts +0 -211
  218. package/routes/comfy.ts +0 -43
  219. package/routes/edit.ts +0 -352
  220. package/routes/generate.ts +0 -492
  221. package/routes/grok.ts +0 -24
  222. package/routes/health.ts +0 -123
  223. package/routes/history.ts +0 -221
  224. package/routes/imageImport.ts +0 -37
  225. package/routes/index.ts +0 -48
  226. package/routes/metadata.ts +0 -77
  227. package/routes/multimode.ts +0 -499
  228. package/routes/nodes.ts +0 -578
  229. package/routes/promptBuilder.ts +0 -37
  230. package/routes/promptImport.ts +0 -379
  231. package/routes/prompts.ts +0 -428
  232. package/routes/sessions.ts +0 -317
  233. package/routes/storage.ts +0 -47
  234. package/routes/video.ts +0 -232
  235. package/server.ts +0 -290
  236. package/ui/dist/assets/SettingsWorkspace-F3eNu3mJ.js +0 -1
  237. package/ui/dist/assets/index-B6tcw_UF.css +0 -1
  238. package/ui/dist/assets/index-DYOh6gQD.js +0 -32
  239. package/vendor/progrok-0.1.1.tgz +0 -0
@@ -1,236 +0,0 @@
1
- import { ulid } from "ulid";
2
- import { getRoleTemplate } from "./cardNewsRoleTemplateStore.js";
3
- import { getImageTemplate } from "./cardNewsTemplateStore.js";
4
- import { buildCardNewsPlannerMessages } from "./cardNewsPlannerPrompt.js";
5
- import { requestCardNewsPlannerJson } from "./cardNewsPlannerClient.js";
6
- import { repairPlannerOutput, validatePlannerOutput } from "./cardNewsPlannerSchema.js";
7
- import { waitForOAuthReady } from "./oauthProxy.js";
8
- import type { RouteRuntimeContext } from "./runtimeContext.js";
9
-
10
- import { errInfo } from "./errInfo.js";
11
-
12
- interface RoleEntry {
13
- role: string;
14
- required: boolean;
15
- promptHint: string;
16
- preferredSlots: string[];
17
- }
18
-
19
- interface RoleTemplate {
20
- id: string;
21
- name: string;
22
- defaultCount: number;
23
- roles: RoleEntry[];
24
- }
25
-
26
- interface CardNewsBriefInput {
27
- setId?: string;
28
- topic?: string;
29
- title?: string;
30
- audience?: string;
31
- goal?: string;
32
- contentBrief?: string;
33
- imageTemplateId?: string;
34
- roleTemplateId?: string;
35
- size?: string;
36
- }
37
-
38
- interface BriefForBody {
39
- audience?: string;
40
- goal?: string;
41
- content?: string;
42
- }
43
-
44
- interface PlannerCardOutput {
45
- role: string;
46
- headline: string;
47
- body: string;
48
- visualPrompt: string;
49
- textFields?: unknown;
50
- references?: unknown[];
51
- order?: number;
52
- }
53
-
54
- interface PlannerOutput {
55
- title: string;
56
- topic: string;
57
- audience?: string;
58
- goal?: string;
59
- cards: PlannerCardOutput[];
60
- }
61
-
62
- function compactText(value: unknown, fallback: string): string {
63
- const text = typeof value === "string" ? value.trim() : "";
64
- return text || fallback;
65
- }
66
-
67
- function detectBriefLanguage(input: CardNewsBriefInput): "ko" | "en" | "und" {
68
- const text = [input.topic, input.audience, input.goal, input.contentBrief].filter(Boolean).join(" ");
69
- if (/[가-힣]/.test(text)) return "ko";
70
- if (/[A-Za-z]/.test(text)) return "en";
71
- return "und";
72
- }
73
-
74
- function fallbackLabel(role: string, lang: string): string {
75
- const ko: Record<string, string> = {
76
- cta: "다음 행동",
77
- problem: "왜 중요한가",
78
- insight: "핵심 인사이트",
79
- example: "예시로 보기",
80
- data: "숫자로 확인",
81
- summary: "요약",
82
- };
83
- const en: Record<string, string> = {
84
- cta: "Next action",
85
- problem: "Why it matters",
86
- insight: "Key insight",
87
- example: "Example",
88
- data: "By the numbers",
89
- summary: "Summary",
90
- };
91
- if (lang === "ko") return ko[role] || role;
92
- if (lang === "en") return en[role] || role;
93
- return "";
94
- }
95
-
96
- function headlineFor(role: string, topic: string, lang: string): string {
97
- const label = compactText(topic, "Card news");
98
- if (role === "cover" || role === "hook") return label;
99
- return fallbackLabel(role, lang) || label;
100
- }
101
-
102
- function bodyFor(role: string, brief: BriefForBody, lang: string): string {
103
- const content = compactText(brief.content, "");
104
- if (content) return content;
105
- const target = compactText(brief.audience, lang === "ko" ? "독자" : "reader");
106
- const goal = compactText(brief.goal, lang === "ko" ? "핵심 메시지" : "the key message");
107
- if (lang === "ko") {
108
- if (role === "cta") return `${target}가 바로 실행할 수 있는 다음 단계를 제안합니다.`;
109
- if (role === "problem") return `${target}가 겪는 문제를 짧고 분명하게 보여줍니다.`;
110
- if (role === "insight") return `${goal}을 이해하기 쉬운 한 문장으로 정리합니다.`;
111
- return `${goal}을 카드 역할에 맞춰 전달합니다.`;
112
- }
113
- if (role === "cta") return `Suggest a next step ${target} can take immediately.`;
114
- if (role === "problem") return `Show the problem ${target} faces in a concise way.`;
115
- if (role === "insight") return `Explain ${goal} in one clear sentence.`;
116
- return `Present ${goal} for this card.`;
117
- }
118
-
119
- function normalizeTextFields(fields: unknown): unknown[] {
120
- return Array.isArray(fields) ? fields : [];
121
- }
122
-
123
- function toCardNewsPlan(plannerOutput: PlannerOutput, input: CardNewsBriefInput, roleTemplate: RoleTemplate) {
124
- const topic = compactText(plannerOutput.topic, compactText(input.topic, input.title || "Untitled card news"));
125
- return {
126
- setId: input.setId || `cs_${ulid()}`,
127
- title: compactText(plannerOutput.title, topic),
128
- topic,
129
- imageTemplateId: input.imageTemplateId || "academy-lesson-square",
130
- roleTemplateId: roleTemplate.id,
131
- size: input.size || "2048x2048",
132
- generationStrategy: "parallel-template-i2i",
133
- cards: plannerOutput.cards.map((card, index) => ({
134
- id: `card_${index + 1}`,
135
- order: index + 1,
136
- role: card.role,
137
- headline: card.headline,
138
- body: card.body,
139
- visualPrompt: card.visualPrompt,
140
- textFields: normalizeTextFields(card.textFields),
141
- templateSlotAssignments: {
142
- title: "headline",
143
- body: "body",
144
- image: "visual",
145
- },
146
- references: card.references || [],
147
- locked: false,
148
- status: "draft",
149
- })),
150
- };
151
- }
152
-
153
- export function createDeterministicCardNewsDraft(input: CardNewsBriefInput = {}) {
154
- const roleTemplate = getRoleTemplate(input.roleTemplateId) as RoleTemplate;
155
- const topic = compactText(input.topic, input.title || "Untitled card news");
156
- const title = compactText(input.title, topic);
157
- const brief: BriefForBody = {
158
- audience: input.audience,
159
- goal: input.goal,
160
- content: input.contentBrief,
161
- };
162
- const lang = detectBriefLanguage(input);
163
- const output: PlannerOutput = {
164
- title,
165
- topic,
166
- audience: compactText(input.audience, ""),
167
- goal: compactText(input.goal, ""),
168
- cards: roleTemplate.roles.map((role, idx) => ({
169
- order: idx + 1,
170
- role: role.role,
171
- headline: headlineFor(role.role, topic, lang),
172
- body: bodyFor(role.role, brief, lang),
173
- visualPrompt: `${role.promptHint}, ${topic}`,
174
- textFields: [],
175
- references: [],
176
- })),
177
- };
178
- return toCardNewsPlan(output, input, roleTemplate);
179
- }
180
-
181
- type PlannerError = Error & { code?: string; status?: number };
182
-
183
- function plannerError(message: string, code: string, status: number): PlannerError {
184
- const err = new Error(message) as PlannerError;
185
- err.code = code;
186
- err.status = status;
187
- return err;
188
- }
189
-
190
- export async function createCardNewsDraft(ctxOrInput: RouteRuntimeContext | CardNewsBriefInput = {} as CardNewsBriefInput, maybeInput: CardNewsBriefInput = {}) {
191
- const hasCtx = Boolean((ctxOrInput as RouteRuntimeContext | undefined)?.config);
192
- const ctx = hasCtx ? (ctxOrInput as RouteRuntimeContext) : null;
193
- const input = hasCtx ? maybeInput : (ctxOrInput as CardNewsBriefInput);
194
- const roleTemplate = getRoleTemplate(input.roleTemplateId) as RoleTemplate;
195
-
196
- if (!ctx) return createDeterministicCardNewsDraft(input);
197
- const planner = (ctx.config as { cardNewsPlanner?: { enabled?: boolean; model?: string; timeoutMs?: number; deterministicFallback?: boolean } } | undefined)?.cardNewsPlanner;
198
- if (!planner?.enabled) {
199
- return {
200
- plan: createDeterministicCardNewsDraft(input),
201
- planner: { mode: "deterministic-fallback", model: "none", repaired: false },
202
- };
203
- }
204
-
205
- const imageTemplate = await getImageTemplate(ctx, input.imageTemplateId || "academy-lesson-square");
206
- try {
207
- await waitForOAuthReady(ctx);
208
- const messages = buildCardNewsPlannerMessages({ ...input, roleTemplate, imageTemplate });
209
- const raw = await requestCardNewsPlannerJson({ messages }, {
210
- oauthUrl: (ctx as RouteRuntimeContext & { oauthUrl?: string }).oauthUrl,
211
- model: planner.model,
212
- timeoutMs: planner.timeoutMs,
213
- });
214
- let result = validatePlannerOutput(raw.output, roleTemplate);
215
- if (!result.ok) result = repairPlannerOutput(raw.output, { ...input, roleTemplate });
216
- if (!result.ok) throw plannerError("Planner schema invalid", "PLANNER_SCHEMA_INVALID", 422);
217
- return {
218
- plan: toCardNewsPlan(result.plan as PlannerOutput, input, roleTemplate),
219
- planner: { mode: raw.mode, model: raw.model, repaired: result.repaired },
220
- };
221
- } catch (e) {
222
- const err = errInfo(e);
223
- if (planner.deterministicFallback) {
224
- return {
225
- plan: createDeterministicCardNewsDraft(input),
226
- planner: {
227
- mode: "deterministic-fallback",
228
- model: planner.model,
229
- repaired: true,
230
- },
231
- };
232
- }
233
- if (err.code) throw err.raw;
234
- throw plannerError(err.message || "Planner unavailable", "PLANNER_UNAVAILABLE", 503);
235
- }
236
- }
@@ -1,155 +0,0 @@
1
- import { config } from "../config.js";
2
- import { CARD_NEWS_PLANNER_SCHEMA } from "./cardNewsPlannerSchema.js";
3
- import { logEvent } from "./logger.js";
4
-
5
- type PlannerError = Error & {
6
- code?: string;
7
- status?: number;
8
- upstreamStatus?: number;
9
- upstreamBodyChars?: number;
10
- };
11
-
12
- interface PlannerMessage {
13
- role: string;
14
- content: unknown;
15
- }
16
-
17
- interface PlannerRequestOptions {
18
- oauthUrl: string;
19
- model: string;
20
- messages: PlannerMessage[];
21
- timeoutMs: number;
22
- structured?: boolean;
23
- reasoningEffort?: string;
24
- }
25
-
26
- interface PlannerInput {
27
- messages: PlannerMessage[];
28
- }
29
-
30
- interface PlannerCallOptions {
31
- oauthUrl?: string;
32
- model?: string;
33
- timeoutMs?: number;
34
- reasoningEffort?: string;
35
- }
36
-
37
- function plannerError(message: string, code: string, status = 502): PlannerError {
38
- const err = new Error(message) as PlannerError;
39
- err.code = code;
40
- err.status = status;
41
- return err;
42
- }
43
-
44
- function extractText(json: unknown): string {
45
- if (!json || typeof json !== "object") return "";
46
- const obj = json as Record<string, unknown>;
47
- if (typeof obj.output_text === "string") return obj.output_text;
48
- const output = Array.isArray(obj.output) ? (obj.output as unknown[]) : [];
49
- for (const item of output) {
50
- if (!item || typeof item !== "object") continue;
51
- const contents = (item as Record<string, unknown>).content;
52
- if (!Array.isArray(contents)) continue;
53
- for (const content of contents as unknown[]) {
54
- if (!content || typeof content !== "object") continue;
55
- const c = content as Record<string, unknown>;
56
- if (typeof c.text === "string") return c.text;
57
- if (typeof c.value === "string") return c.value;
58
- }
59
- }
60
- return "";
61
- }
62
-
63
- async function requestJson({ oauthUrl, model, messages, timeoutMs, structured, reasoningEffort }: PlannerRequestOptions): Promise<string> {
64
- const controller = new AbortController();
65
- const timer = setTimeout(() => controller.abort(), timeoutMs);
66
- try {
67
- const body = {
68
- model,
69
- input: messages,
70
- stream: false,
71
- ...(reasoningEffort ? { reasoning: { effort: reasoningEffort } } : {}),
72
- ...(structured
73
- ? {
74
- text: {
75
- format: {
76
- type: "json_schema",
77
- name: "card_news_planner_output",
78
- strict: true,
79
- schema: CARD_NEWS_PLANNER_SCHEMA,
80
- },
81
- },
82
- }
83
- : { text: { format: { type: "json_object" } } }),
84
- };
85
- const res = await fetch(`${oauthUrl}/v1/responses`, {
86
- method: "POST",
87
- headers: { "Content-Type": "application/json" },
88
- signal: controller.signal,
89
- body: JSON.stringify(body),
90
- });
91
- logEvent("card-news-planner", "response", { model, status: res.status, structured });
92
- if (!res.ok) {
93
- const text = await res.text();
94
- const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
95
- err.upstreamStatus = res.status;
96
- err.upstreamBodyChars = text.length;
97
- throw err;
98
- }
99
- const json: unknown = await res.json();
100
- return extractText(json);
101
- } finally {
102
- clearTimeout(timer);
103
- }
104
- }
105
-
106
- async function requestChatJson({ oauthUrl, model, messages, timeoutMs }: Omit<PlannerRequestOptions, "structured" | "reasoningEffort">): Promise<string> {
107
- const controller = new AbortController();
108
- const timer = setTimeout(() => controller.abort(), timeoutMs);
109
- try {
110
- const res = await fetch(`${oauthUrl}/v1/chat/completions`, {
111
- method: "POST",
112
- headers: { "Content-Type": "application/json" },
113
- signal: controller.signal,
114
- body: JSON.stringify({
115
- model,
116
- messages,
117
- response_format: { type: "json_object" },
118
- }),
119
- });
120
- logEvent("card-news-planner", "chat_response", { model, status: res.status });
121
- if (!res.ok) {
122
- const text = await res.text();
123
- const err = plannerError("Planner upstream failed", "PLANNER_UPSTREAM_FAILED", 502);
124
- err.upstreamStatus = res.status;
125
- err.upstreamBodyChars = text.length;
126
- throw err;
127
- }
128
- const json = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
129
- return json.choices?.[0]?.message?.content || "";
130
- } finally {
131
- clearTimeout(timer);
132
- }
133
- }
134
-
135
- export async function requestCardNewsPlannerJson(input: PlannerInput, options: PlannerCallOptions = {}) {
136
- const oauthUrl = options.oauthUrl || `http://127.0.0.1:${config.oauth.proxyPort}`;
137
- const model = options.model || config.cardNewsPlanner.model;
138
- const timeoutMs = options.timeoutMs || config.cardNewsPlanner.timeoutMs;
139
- const reasoningEffort = options.reasoningEffort || config.imageModels?.reasoningEffort || "medium";
140
- let text = "";
141
- let mode = "structured-output";
142
- try {
143
- text = await requestJson({ oauthUrl, model, messages: input.messages, timeoutMs, structured: true, reasoningEffort });
144
- } catch (err) {
145
- const code = (err as { code?: unknown })?.code;
146
- if (code !== "PLANNER_UPSTREAM_FAILED") throw err;
147
- mode = "json-mode";
148
- text = await requestChatJson({ oauthUrl, model, messages: input.messages, timeoutMs });
149
- }
150
- try {
151
- return { output: JSON.parse(text), mode, model };
152
- } catch {
153
- throw plannerError("Planner returned invalid JSON", "PLANNER_INVALID_JSON", 502);
154
- }
155
- }
@@ -1,62 +0,0 @@
1
- export function buildCardNewsPlannerMessages(input: any = {}) {
2
- const roleTemplate: any = input.roleTemplate || {};
3
- const imageTemplate: any = input.imageTemplate || {};
4
- return [
5
- {
6
- role: "developer",
7
- content: [
8
- "You are a Card News planning assistant.",
9
- "Return JSON only. Do not call image tools. Do not generate images.",
10
- "Keep the selected card count and role order exactly.",
11
- "Preserve the user's original language for headline, body, textFields, and visualPrompt.",
12
- "Do not translate to English unless explicitly requested.",
13
- "If the user mixes languages, preserve the mix.",
14
- "If the user provides exact text, preserve it exactly.",
15
- "Role names such as cover/problem/cta are structural labels, not visible design text.",
16
- "Keep headline and body as UI/manifest text.",
17
- "Only textFields[].text with renderMode=\"in-image\" is intended to appear inside the image.",
18
- "When visible text needs a specific language, textFields[].text must contain the exact words in that language/script.",
19
- "Do not translate, romanize, summarize, or replace visible text.",
20
- "Create textFields only for text that should be readable inside the image.",
21
- "Prefer template slot ids and placements when assigning textFields.",
22
- "Use headline textFields for hook/cover cards, body or caption textFields for explanation cards, and cta textFields only for action cards.",
23
- "Never put structural role labels such as CTA, cover, problem, insight, or example into textFields unless the user explicitly requested that exact visible wording.",
24
- "Placement examples: top-right badge, top-center headline, center-left supporting caption, bottom-center CTA.",
25
- "Make visualPrompt describe scene, layout, style, spatial composition, and text-box placement only.",
26
- "Do not duplicate visible text in visualPrompt except to reference a text box position.",
27
- "visualPrompt may mention a text box location, but the exact readable copy must live only in textFields[].text.",
28
- ].join("\n"),
29
- },
30
- {
31
- role: "user",
32
- content: JSON.stringify({
33
- topic: input.topic || "",
34
- audience: input.audience || "",
35
- goal: input.goal || "",
36
- contentBrief: input.contentBrief || "",
37
- size: input.size || "2048x2048",
38
- imageTemplate: {
39
- id: imageTemplate.id,
40
- name: imageTemplate.name,
41
- stylePrompt: imageTemplate.stylePrompt,
42
- recommendedOutputSizes: imageTemplate.recommendedOutputSizes || [],
43
- slots: imageTemplate.slots || [],
44
- palette: imageTemplate.palette || [],
45
- },
46
- roleTemplate: {
47
- id: roleTemplate.id,
48
- roles: (roleTemplate.roles || []).map((role: any) => ({
49
- role: role.role,
50
- promptHint: role.promptHint,
51
- preferredSlots: role.preferredSlots || [],
52
- })),
53
- },
54
- textFieldPolicy: {
55
- visibleTextSource: "textFields[].text",
56
- renderableMode: "in-image",
57
- structuralRoleLabelsAreNotVisibleText: true,
58
- },
59
- }),
60
- },
61
- ];
62
- }