@purposeinplay/payload-ai-translate 0.1.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 (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,103 @@
1
+ export type TranslationJobStatus = 'running' | 'completed' | 'failed';
2
+ export type TranslationJob = {
3
+ jobId: string;
4
+ collection: string;
5
+ documentId: string | number;
6
+ totalLocales: number;
7
+ completedLocales: string[];
8
+ failedLocales: {
9
+ locale: string;
10
+ error: string;
11
+ }[];
12
+ status: TranslationJobStatus;
13
+ startedAt: number;
14
+ };
15
+ export type JobProgressEvent = {
16
+ jobId: string;
17
+ completed: string[];
18
+ failed: {
19
+ locale: string;
20
+ error: string;
21
+ }[];
22
+ total: number;
23
+ status: TranslationJobStatus;
24
+ };
25
+ export type Subscriber = (event: JobProgressEvent) => void;
26
+ /**
27
+ * Doc-level subscriber. Fires once per `createJob` for a matching
28
+ * (collection, documentId) pair, with the new job's first event.
29
+ * Lets the SSE endpoint hold an open stream for "any job on this doc"
30
+ * and push immediately when one starts — no client polling needed.
31
+ */
32
+ export type DocSubscriber = (event: JobProgressEvent) => void;
33
+ /**
34
+ * Optional persistence target. When the plugin's `persistJobs: true`
35
+ * is set, plugin.ts calls `setPersistenceContext()` at boot with the
36
+ * Payload instance + jobs-collection slug. Every mutation to `jobs`
37
+ * (create/update/cleanup) then also mirrors to the sidecar collection.
38
+ *
39
+ * Mirror writes are fire-and-forget — failures don't roll back the
40
+ * in-memory mutation. The persistence layer is for VISIBILITY across
41
+ * restarts; the actual translation closures still live in process
42
+ * memory and don't resume after a restart.
43
+ */
44
+ type PersistenceContext = {
45
+ payload: {
46
+ create: (args: unknown) => Promise<unknown>;
47
+ update: (args: unknown) => Promise<unknown>;
48
+ delete: (args: unknown) => Promise<unknown>;
49
+ find: (args: unknown) => Promise<{
50
+ docs: unknown[];
51
+ }>;
52
+ logger?: {
53
+ warn?: (msg: string) => void;
54
+ };
55
+ };
56
+ collectionSlug: string;
57
+ };
58
+ export declare function setPersistenceContext(ctx: PersistenceContext | null): void;
59
+ export declare function createJob(collection: string, documentId: string | number, locales: string[]): string;
60
+ export declare function updateJob(jobId: string, locale: string, success: boolean, error?: string): void;
61
+ export declare function getJob(jobId: string): TranslationJob | undefined;
62
+ /**
63
+ * Atomically return the active job for `(collection, documentId)` or
64
+ * create a new one in a single synchronous step. Replaces the unsafe
65
+ * `getActiveJobForDoc(...) ?? createJob(...)` pattern: two concurrent
66
+ * after-change hook fires for the same doc could both read `undefined`
67
+ * from `getActiveJobForDoc` and both call `createJob`, producing two
68
+ * jobs (and two LLM calls) for the same write.
69
+ *
70
+ * Because Node is single-threaded and this whole function runs
71
+ * synchronously between event-loop turns, the get + set pair is
72
+ * effectively atomic from the caller's perspective. No mutex needed.
73
+ *
74
+ * Returns `{ jobId, created: boolean }`. Callers can branch on
75
+ * `created` to decide whether to fire a new translation closure
76
+ * (`created: true`) or join an in-flight one (`created: false`).
77
+ */
78
+ export declare function getOrCreateJobForDoc(collection: string, documentId: string | number, locales: string[]): {
79
+ jobId: string;
80
+ created: boolean;
81
+ };
82
+ /**
83
+ * Mark a still-running job as `completed` without performing any
84
+ * translation. Used by the after-change hooks when they decide to
85
+ * short-circuit (e.g. `skipAutoOnPublish: true` from `effective-locales`)
86
+ * AFTER a job has already been seeded by a parent flow — without this,
87
+ * the polling UI would show "Translating…" until the TTL sweep
88
+ * (2 minutes). Notifies SSE subscribers so the client can clear its
89
+ * progress widget immediately. No-op if `jobId` isn't tracked.
90
+ */
91
+ export declare function completeJobNow(jobId: string): void;
92
+ export declare function getActiveJobForDoc(collection: string, documentId: string | number): TranslationJob | undefined;
93
+ export declare function subscribe(jobId: string, callback: Subscriber): () => void;
94
+ /**
95
+ * Subscribe to "any new job created for this (collection, documentId)".
96
+ * The callback fires once per new job started after this subscription is
97
+ * registered. Used by the `?docId=X` SSE endpoint to push events to open
98
+ * editors the moment a translation begins.
99
+ */
100
+ export declare function subscribeToDoc(collection: string, documentId: string | number, callback: DocSubscriber): () => void;
101
+ export declare function cleanup(): void;
102
+ export declare function _resetStore(): void;
103
+ export {};
@@ -0,0 +1,314 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ // ---------------------------------------------------------------------------
3
+ // Store
4
+ // ---------------------------------------------------------------------------
5
+ const jobs = new Map();
6
+ const subscribers = new Map();
7
+ const docSubscribers = new Map();
8
+ let persistenceContext = null;
9
+ export function setPersistenceContext(ctx) {
10
+ persistenceContext = ctx;
11
+ }
12
+ function mirrorCreate(job) {
13
+ if (!persistenceContext) return;
14
+ void persistenceContext.payload.create({
15
+ collection: persistenceContext.collectionSlug,
16
+ data: {
17
+ jobId: job.jobId,
18
+ collection: job.collection,
19
+ documentId: String(job.documentId),
20
+ status: job.status,
21
+ totalLocales: job.totalLocales,
22
+ completedLocales: job.completedLocales,
23
+ failedLocales: job.failedLocales,
24
+ startedAt: new Date(job.startedAt).toISOString()
25
+ },
26
+ overrideAccess: true
27
+ }).catch((err)=>{
28
+ // BUG-29 fix: don't silently swallow. Under bursty parallel
29
+ // translates (e.g. bulk-publish of 5+ docs), one of the
30
+ // concurrent `payload.create` calls on `ai_translate_jobs` can
31
+ // hit a transient DB conflict or transaction race; pre-fix this
32
+ // silently dropped the row and the admin Jobs page lost one
33
+ // out of N entries. We log loud + warn so ops can see it
34
+ // happened; the in-memory job is still correct so translation
35
+ // proceeds.
36
+ persistenceContext?.payload.logger?.warn?.(`[ai-translate] mirrorCreate for ai_translate_jobs failed for ${job.collection}/${String(job.documentId)} jobId=${job.jobId}: ${err instanceof Error ? err.message : 'Unknown error'}. The in-memory job remains active; the admin Jobs page may be missing this row.`);
37
+ });
38
+ }
39
+ function mirrorUpdate(job) {
40
+ if (!persistenceContext) return;
41
+ // Find by jobId then update — Payload doesn't expose upsert.
42
+ void persistenceContext.payload.find({
43
+ collection: persistenceContext.collectionSlug,
44
+ where: {
45
+ jobId: {
46
+ equals: job.jobId
47
+ }
48
+ },
49
+ limit: 1,
50
+ depth: 0,
51
+ overrideAccess: true
52
+ }).then(async (result)=>{
53
+ const first = result.docs[0];
54
+ if (!first?.id) return;
55
+ await persistenceContext.payload.update({
56
+ collection: persistenceContext.collectionSlug,
57
+ id: first.id,
58
+ data: {
59
+ status: job.status,
60
+ completedLocales: job.completedLocales,
61
+ failedLocales: job.failedLocales
62
+ },
63
+ overrideAccess: true
64
+ });
65
+ }).catch(()=>{});
66
+ }
67
+ function mirrorDelete(jobId) {
68
+ if (!persistenceContext) return;
69
+ void persistenceContext.payload.find({
70
+ collection: persistenceContext.collectionSlug,
71
+ where: {
72
+ jobId: {
73
+ equals: jobId
74
+ }
75
+ },
76
+ limit: 1,
77
+ depth: 0,
78
+ overrideAccess: true
79
+ }).then(async (result)=>{
80
+ const first = result.docs[0];
81
+ if (!first?.id) return;
82
+ await persistenceContext.payload.delete({
83
+ collection: persistenceContext.collectionSlug,
84
+ id: first.id,
85
+ overrideAccess: true
86
+ });
87
+ }).catch(()=>{});
88
+ }
89
+ // Background cleanup interval. Lazily armed on the first `createJob` so a
90
+ // long-idle process (no translations ever) doesn't keep a timer alive. The
91
+ // previous behavior only ran `cleanup()` on new-job arrival, so finished
92
+ // jobs from a short burst would sit in memory forever during quiet periods.
93
+ let cleanupTimer = null;
94
+ const CLEANUP_INTERVAL_MS = 60_000; // 1 min; jobs expire at 2 min
95
+ function armCleanupTimer() {
96
+ if (cleanupTimer !== null) return;
97
+ cleanupTimer = setInterval(()=>{
98
+ cleanup();
99
+ // Disarm when the store is fully drained so the timer doesn't keep the
100
+ // process alive in test runners or short-lived scripts.
101
+ if (jobs.size === 0) {
102
+ if (cleanupTimer) {
103
+ clearInterval(cleanupTimer);
104
+ cleanupTimer = null;
105
+ }
106
+ }
107
+ }, CLEANUP_INTERVAL_MS);
108
+ // Allow the Node event loop to exit even if the timer is still scheduled.
109
+ cleanupTimer.unref?.();
110
+ }
111
+ function docKey(collection, documentId) {
112
+ return `${collection}::${String(documentId)}`;
113
+ }
114
+ const JOB_TTL_MS = 120_000; // 2 minutes
115
+ function toEvent(job) {
116
+ return {
117
+ jobId: job.jobId,
118
+ completed: [
119
+ ...job.completedLocales
120
+ ],
121
+ failed: [
122
+ ...job.failedLocales
123
+ ],
124
+ total: job.totalLocales,
125
+ status: job.status
126
+ };
127
+ }
128
+ function notifySubscribers(jobId) {
129
+ const job = jobs.get(jobId);
130
+ const subs = subscribers.get(jobId);
131
+ if (!job || !subs || subs.size === 0) return;
132
+ const event = toEvent(job);
133
+ for (const callback of subs){
134
+ try {
135
+ callback(event);
136
+ } catch {
137
+ // Subscriber errors never break the store
138
+ }
139
+ }
140
+ }
141
+ function notifyDocSubscribers(job) {
142
+ const subs = docSubscribers.get(docKey(job.collection, job.documentId));
143
+ if (!subs || subs.size === 0) return;
144
+ const event = toEvent(job);
145
+ for (const callback of subs){
146
+ try {
147
+ callback(event);
148
+ } catch {
149
+ // Subscriber errors never break the store
150
+ }
151
+ }
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Public API
155
+ // ---------------------------------------------------------------------------
156
+ export function createJob(collection, documentId, locales) {
157
+ cleanup();
158
+ armCleanupTimer();
159
+ const jobId = randomUUID();
160
+ const job = {
161
+ jobId,
162
+ collection,
163
+ documentId,
164
+ totalLocales: locales.length,
165
+ completedLocales: [],
166
+ failedLocales: [],
167
+ status: 'running',
168
+ startedAt: Date.now()
169
+ };
170
+ jobs.set(jobId, job);
171
+ mirrorCreate(job);
172
+ // Notify any open `?docId=X` SSE streams that a new job has started
173
+ // for this document. The client can then mount its progress UI
174
+ // immediately — no polling delay.
175
+ notifyDocSubscribers(job);
176
+ return jobId;
177
+ }
178
+ export function updateJob(jobId, locale, success, error) {
179
+ const job = jobs.get(jobId);
180
+ if (!job) return;
181
+ // BUG-29 fix: dedup before append. Under bursty parallel translates
182
+ // a retry path or duplicate fire could call updateJob twice with the
183
+ // same locale, producing `completed_locales: ["es", "es"]`. Set-style
184
+ // dedup keeps the array correct under all event orderings.
185
+ if (success) {
186
+ if (!job.completedLocales.includes(locale)) {
187
+ job.completedLocales.push(locale);
188
+ }
189
+ } else {
190
+ const alreadyFailed = job.failedLocales.some((f)=>f.locale === locale);
191
+ if (!alreadyFailed) {
192
+ job.failedLocales.push({
193
+ locale,
194
+ error: error ?? 'Unknown error'
195
+ });
196
+ }
197
+ }
198
+ const done = job.completedLocales.length + job.failedLocales.length;
199
+ if (done >= job.totalLocales) {
200
+ job.status = job.failedLocales.length > 0 ? 'failed' : 'completed';
201
+ }
202
+ mirrorUpdate(job);
203
+ notifySubscribers(jobId);
204
+ }
205
+ export function getJob(jobId) {
206
+ return jobs.get(jobId);
207
+ }
208
+ /**
209
+ * Atomically return the active job for `(collection, documentId)` or
210
+ * create a new one in a single synchronous step. Replaces the unsafe
211
+ * `getActiveJobForDoc(...) ?? createJob(...)` pattern: two concurrent
212
+ * after-change hook fires for the same doc could both read `undefined`
213
+ * from `getActiveJobForDoc` and both call `createJob`, producing two
214
+ * jobs (and two LLM calls) for the same write.
215
+ *
216
+ * Because Node is single-threaded and this whole function runs
217
+ * synchronously between event-loop turns, the get + set pair is
218
+ * effectively atomic from the caller's perspective. No mutex needed.
219
+ *
220
+ * Returns `{ jobId, created: boolean }`. Callers can branch on
221
+ * `created` to decide whether to fire a new translation closure
222
+ * (`created: true`) or join an in-flight one (`created: false`).
223
+ */ export function getOrCreateJobForDoc(collection, documentId, locales) {
224
+ const existing = getActiveJobForDoc(collection, documentId);
225
+ if (existing) return {
226
+ jobId: existing.jobId,
227
+ created: false
228
+ };
229
+ const jobId = createJob(collection, documentId, locales);
230
+ return {
231
+ jobId,
232
+ created: true
233
+ };
234
+ }
235
+ /**
236
+ * Mark a still-running job as `completed` without performing any
237
+ * translation. Used by the after-change hooks when they decide to
238
+ * short-circuit (e.g. `skipAutoOnPublish: true` from `effective-locales`)
239
+ * AFTER a job has already been seeded by a parent flow — without this,
240
+ * the polling UI would show "Translating…" until the TTL sweep
241
+ * (2 minutes). Notifies SSE subscribers so the client can clear its
242
+ * progress widget immediately. No-op if `jobId` isn't tracked.
243
+ */ export function completeJobNow(jobId) {
244
+ const job = jobs.get(jobId);
245
+ if (!job) return;
246
+ if (job.status !== 'running') return; // already terminal
247
+ job.status = 'completed';
248
+ mirrorUpdate(job);
249
+ notifySubscribers(jobId);
250
+ }
251
+ export function getActiveJobForDoc(collection, documentId) {
252
+ for (const job of jobs.values()){
253
+ if (job.collection === collection && String(job.documentId) === String(documentId) && job.status === 'running') {
254
+ return job;
255
+ }
256
+ }
257
+ return undefined;
258
+ }
259
+ export function subscribe(jobId, callback) {
260
+ if (!subscribers.has(jobId)) {
261
+ subscribers.set(jobId, new Set());
262
+ }
263
+ subscribers.get(jobId).add(callback);
264
+ return ()=>{
265
+ const subs = subscribers.get(jobId);
266
+ if (subs) {
267
+ subs.delete(callback);
268
+ if (subs.size === 0) {
269
+ subscribers.delete(jobId);
270
+ }
271
+ }
272
+ };
273
+ }
274
+ /**
275
+ * Subscribe to "any new job created for this (collection, documentId)".
276
+ * The callback fires once per new job started after this subscription is
277
+ * registered. Used by the `?docId=X` SSE endpoint to push events to open
278
+ * editors the moment a translation begins.
279
+ */ export function subscribeToDoc(collection, documentId, callback) {
280
+ const key = docKey(collection, documentId);
281
+ if (!docSubscribers.has(key)) {
282
+ docSubscribers.set(key, new Set());
283
+ }
284
+ docSubscribers.get(key).add(callback);
285
+ return ()=>{
286
+ const subs = docSubscribers.get(key);
287
+ if (subs) {
288
+ subs.delete(callback);
289
+ if (subs.size === 0) {
290
+ docSubscribers.delete(key);
291
+ }
292
+ }
293
+ };
294
+ }
295
+ export function cleanup() {
296
+ const now = Date.now();
297
+ for (const [jobId, job] of jobs){
298
+ if (now - job.startedAt > JOB_TTL_MS) {
299
+ jobs.delete(jobId);
300
+ subscribers.delete(jobId);
301
+ mirrorDelete(jobId);
302
+ }
303
+ }
304
+ }
305
+ // For testing
306
+ export function _resetStore() {
307
+ jobs.clear();
308
+ subscribers.clear();
309
+ docSubscribers.clear();
310
+ if (cleanupTimer) {
311
+ clearInterval(cleanupTimer);
312
+ cleanupTimer = null;
313
+ }
314
+ }
@@ -0,0 +1,3 @@
1
+ export declare function createRateLimiter(rpm: number): {
2
+ acquire(): Promise<void>;
3
+ };
@@ -0,0 +1,53 @@
1
+ export function createRateLimiter(rpm) {
2
+ let tokens = rpm;
3
+ const refillRate = rpm / 60; // tokens per second
4
+ let lastRefill = Date.now();
5
+ const waiters = [];
6
+ let refillTimer = null;
7
+ function refill() {
8
+ const now = Date.now();
9
+ const elapsed = (now - lastRefill) / 1000;
10
+ tokens = Math.min(rpm, tokens + elapsed * refillRate);
11
+ lastRefill = now;
12
+ }
13
+ function drainWaiters() {
14
+ while(waiters.length > 0 && tokens >= 1){
15
+ tokens -= 1;
16
+ const resolve = waiters.shift();
17
+ resolve();
18
+ }
19
+ }
20
+ function scheduleRefillCheck() {
21
+ // Clean up any existing timer to prevent leaks
22
+ if (refillTimer !== null) {
23
+ clearTimeout(refillTimer);
24
+ refillTimer = null;
25
+ }
26
+ if (waiters.length === 0) {
27
+ return;
28
+ }
29
+ const msPerToken = 1000 / refillRate;
30
+ refillTimer = setTimeout(()=>{
31
+ refillTimer = null;
32
+ refill();
33
+ drainWaiters();
34
+ scheduleRefillCheck();
35
+ }, msPerToken);
36
+ }
37
+ return {
38
+ acquire () {
39
+ refill();
40
+ if (tokens >= 1) {
41
+ tokens -= 1;
42
+ return Promise.resolve();
43
+ }
44
+ return new Promise((resolve)=>{
45
+ const wasEmpty = waiters.length === 0;
46
+ waiters.push(resolve);
47
+ if (wasEmpty) {
48
+ scheduleRefillCheck();
49
+ }
50
+ });
51
+ }
52
+ };
53
+ }
@@ -0,0 +1,43 @@
1
+ import type { TranslatableField } from '../types.js';
2
+ /**
3
+ * Capture ONLY the localized translatable fields from a doc, stored
4
+ * as a nested object payload that can be passed directly to
5
+ * `payload.update({ data: snapshot })` during the revert action.
6
+ *
7
+ * Security (Decision #30 + F-SEC-SNAPSHOT): Payload's `findByID`
8
+ * returns the full document, which includes `password` hashes,
9
+ * `email`, `roles`, and other `excludeFields` content. Storing that
10
+ * into `bulk-translate-units.preRunSnapshot` jsonb would leak
11
+ * sensitive data into a queryable admin collection. This helper:
12
+ * 1. Rejects any path whose top-level segment is in the forbidden
13
+ * denylist (runtime guard, not just convention).
14
+ * 2. Scopes captured keys to ONLY the paths the caller provided.
15
+ *
16
+ * Shape (Decision #22 v2 / F-DA-REVERT round-trip): paths with dots
17
+ * (e.g. `meta.description`) are stored as NESTED objects
18
+ * (`{ meta: { description: <value> } }`), not as flat keys with
19
+ * literal dots. This makes the snapshot a valid `payload.update`
20
+ * payload directly — revert simply spreads it into `data`.
21
+ *
22
+ * Array-index paths (`sections.0.title`) are intentionally rejected
23
+ * — they expand via the existing resolveTranslatableFields walker
24
+ * before this helper sees them. The CALLER passes the shape paths
25
+ * (`title`, `sections`); this helper does NOT understand per-index
26
+ * traversal.
27
+ */
28
+ export declare function captureLocalizedSnapshot(doc: Record<string, unknown>, localizedFieldPaths: readonly string[]): Record<string, unknown>;
29
+ /**
30
+ * Schema-hash of the localized translatable fields. Used by the
31
+ * revert action: if the schema changed between snapshot and replay
32
+ * (field renamed, type changed, field de-localized, field removed),
33
+ * the hash differs and the revert refuses unless the admin sets
34
+ * `force: true` (Decision #34 + F-DA-REVERT-SCHEMA).
35
+ *
36
+ * Hash includes `(path, type, localized)` tuples, sorted, so order
37
+ * doesn't matter. The `localized` flag is included because
38
+ * de-localizing a field (true → false) silently breaks revert: the
39
+ * snapshot data lives in a locale-scoped jsonb but the field on the
40
+ * doc is now global, and replaying writes either error or land in
41
+ * the wrong slot. Including the flag in the hash catches this.
42
+ */
43
+ export declare function hashLocalizedSchema(fields: readonly TranslatableField[]): string;
@@ -0,0 +1,108 @@
1
+ import { hashFieldContent } from './content-hash.js';
2
+ /**
3
+ * Hard denylist of field names that must NEVER appear in a snapshot.
4
+ * Converts F-SEC-SNAPSHOT from a documentation convention into a
5
+ * runtime invariant — a future call site that passes these by mistake
6
+ * will throw at snapshot time rather than silently leak them into a
7
+ * queryable `bulk-translate-units.preRunSnapshot` jsonb column.
8
+ */ const FORBIDDEN_TOP_LEVEL_KEYS = new Set([
9
+ 'password',
10
+ 'email',
11
+ 'emailVerified',
12
+ 'roles',
13
+ 'salt',
14
+ 'hash',
15
+ 'apiKey',
16
+ 'apiKeyIndex',
17
+ 'secret',
18
+ 'token',
19
+ 'resetPasswordToken',
20
+ 'loginAttempts',
21
+ 'lockUntil'
22
+ ]);
23
+ const NUMERIC_SEGMENT_RE = /^\d+$/;
24
+ /**
25
+ * Capture ONLY the localized translatable fields from a doc, stored
26
+ * as a nested object payload that can be passed directly to
27
+ * `payload.update({ data: snapshot })` during the revert action.
28
+ *
29
+ * Security (Decision #30 + F-SEC-SNAPSHOT): Payload's `findByID`
30
+ * returns the full document, which includes `password` hashes,
31
+ * `email`, `roles`, and other `excludeFields` content. Storing that
32
+ * into `bulk-translate-units.preRunSnapshot` jsonb would leak
33
+ * sensitive data into a queryable admin collection. This helper:
34
+ * 1. Rejects any path whose top-level segment is in the forbidden
35
+ * denylist (runtime guard, not just convention).
36
+ * 2. Scopes captured keys to ONLY the paths the caller provided.
37
+ *
38
+ * Shape (Decision #22 v2 / F-DA-REVERT round-trip): paths with dots
39
+ * (e.g. `meta.description`) are stored as NESTED objects
40
+ * (`{ meta: { description: <value> } }`), not as flat keys with
41
+ * literal dots. This makes the snapshot a valid `payload.update`
42
+ * payload directly — revert simply spreads it into `data`.
43
+ *
44
+ * Array-index paths (`sections.0.title`) are intentionally rejected
45
+ * — they expand via the existing resolveTranslatableFields walker
46
+ * before this helper sees them. The CALLER passes the shape paths
47
+ * (`title`, `sections`); this helper does NOT understand per-index
48
+ * traversal.
49
+ */ export function captureLocalizedSnapshot(doc, localizedFieldPaths) {
50
+ const out = {};
51
+ for (const path of localizedFieldPaths){
52
+ const segments = path.split('.');
53
+ const top = segments[0];
54
+ if (top && FORBIDDEN_TOP_LEVEL_KEYS.has(top)) {
55
+ throw new Error(`[ai-translate] captureLocalizedSnapshot: refusing to snapshot sensitive field path "${path}" (top-level key "${top}" is in the F-SEC-SNAPSHOT denylist). Caller must exclude this path before calling.`);
56
+ }
57
+ for (const seg of segments){
58
+ if (NUMERIC_SEGMENT_RE.test(seg)) {
59
+ throw new Error(`[ai-translate] captureLocalizedSnapshot: array-index paths are not supported here. Pass the shape path (e.g. "sections") instead of the indexed path ("${path}"). Array-item traversal lives in resolveTranslatableFields.`);
60
+ }
61
+ }
62
+ const value = resolvePathValue(doc, segments);
63
+ if (value !== undefined) {
64
+ setNestedPath(out, segments, value);
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ /**
70
+ * Schema-hash of the localized translatable fields. Used by the
71
+ * revert action: if the schema changed between snapshot and replay
72
+ * (field renamed, type changed, field de-localized, field removed),
73
+ * the hash differs and the revert refuses unless the admin sets
74
+ * `force: true` (Decision #34 + F-DA-REVERT-SCHEMA).
75
+ *
76
+ * Hash includes `(path, type, localized)` tuples, sorted, so order
77
+ * doesn't matter. The `localized` flag is included because
78
+ * de-localizing a field (true → false) silently breaks revert: the
79
+ * snapshot data lives in a locale-scoped jsonb but the field on the
80
+ * doc is now global, and replaying writes either error or land in
81
+ * the wrong slot. Including the flag in the hash catches this.
82
+ */ export function hashLocalizedSchema(fields) {
83
+ const sorted = [
84
+ ...fields
85
+ ].map((f)=>`${f.path}:${f.type}:${f.localized ? 'l' : 'g'}`).sort().join('\n');
86
+ return hashFieldContent(sorted);
87
+ }
88
+ function resolvePathValue(doc, segments) {
89
+ let cursor = doc;
90
+ for (const part of segments){
91
+ if (cursor === null || cursor === undefined) return undefined;
92
+ if (typeof cursor !== 'object') return undefined;
93
+ cursor = cursor[part];
94
+ }
95
+ return cursor;
96
+ }
97
+ function setNestedPath(target, segments, value) {
98
+ let cursor = target;
99
+ for(let i = 0; i < segments.length - 1; i++){
100
+ const part = segments[i];
101
+ const existing = cursor[part];
102
+ if (existing === null || existing === undefined || typeof existing !== 'object') {
103
+ cursor[part] = {};
104
+ }
105
+ cursor = cursor[part];
106
+ }
107
+ cursor[segments[segments.length - 1]] = value;
108
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import type { TranslateRequest } from '../types.js';
3
+ /**
4
+ * Single source of truth for the prompt + response schema shared by every
5
+ * AI-SDK-backed provider. Previously each provider duplicated this code
6
+ * (~100 lines × 4) with subtle drift.
7
+ *
8
+ * The Zod schema is enforced by `generateObject` — the model is required
9
+ * to produce an `items` array. Single-item, multi-item, doesn't matter:
10
+ * the response shape is structural, not parsed from a JSON string.
11
+ */
12
+ export declare const TranslateResponseSchema: z.ZodObject<{
13
+ items: z.ZodArray<z.ZodObject<{
14
+ id: z.ZodString;
15
+ text: z.ZodString;
16
+ }, z.core.$strip>>;
17
+ }, z.core.$strip>;
18
+ export type TranslateResponseObject = z.infer<typeof TranslateResponseSchema>;
19
+ export declare function buildSystemPrompt(request: TranslateRequest): string;
20
+ export declare function buildUserMessage(request: TranslateRequest): string;
21
+ /**
22
+ * Pre-call estimate of token usage and cost. Heuristic-only — `ai` SDK has
23
+ * no dry-run token counter. Each provider plugs in its own pricing table.
24
+ */
25
+ export declare function estimateUsage(request: TranslateRequest, pricing?: {
26
+ input: number;
27
+ output: number;
28
+ }): {
29
+ inputTokens: number;
30
+ estimatedCostUsd?: number;
31
+ };