@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (163) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +13 -1
  3. package/dist/helpers/integration/api.js +29 -16
  4. package/dist/helpers/integration/api.js.map +2 -2
  5. package/dist/helpers/integration/auth.js +11 -6
  6. package/dist/helpers/integration/auth.js.map +3 -3
  7. package/dist/modules/auth/commands/roles.js +9 -12
  8. package/dist/modules/auth/commands/roles.js.map +2 -2
  9. package/dist/modules/catalog/ai-agents-context.js +147 -0
  10. package/dist/modules/catalog/ai-agents-context.js.map +7 -0
  11. package/dist/modules/catalog/ai-agents.js +383 -0
  12. package/dist/modules/catalog/ai-agents.js.map +7 -0
  13. package/dist/modules/catalog/ai-tools/_shared.js +318 -0
  14. package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
  15. package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
  16. package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
  17. package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
  18. package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
  19. package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
  20. package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
  21. package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
  22. package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
  23. package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
  24. package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
  25. package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
  26. package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
  27. package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
  28. package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
  29. package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
  30. package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
  31. package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
  32. package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
  33. package/dist/modules/catalog/ai-tools/types.js +10 -0
  34. package/dist/modules/catalog/ai-tools/types.js.map +7 -0
  35. package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
  36. package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
  37. package/dist/modules/catalog/ai-tools.js +28 -0
  38. package/dist/modules/catalog/ai-tools.js.map +7 -0
  39. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
  40. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
  41. package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
  42. package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
  43. package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
  44. package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
  45. package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
  46. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  47. package/dist/modules/catalog/events.js +7 -4
  48. package/dist/modules/catalog/events.js.map +2 -2
  49. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
  50. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
  51. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
  52. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
  53. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
  54. package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
  55. package/dist/modules/catalog/widgets/injection-table.js +13 -1
  56. package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
  57. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
  58. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
  59. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
  60. package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
  61. package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
  62. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  63. package/dist/modules/customers/ai-agents-context.js +96 -0
  64. package/dist/modules/customers/ai-agents-context.js.map +7 -0
  65. package/dist/modules/customers/ai-agents.js +244 -0
  66. package/dist/modules/customers/ai-agents.js.map +7 -0
  67. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
  68. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
  69. package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
  70. package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
  71. package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
  72. package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
  73. package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
  74. package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
  75. package/dist/modules/customers/ai-tools/people-pack.js +261 -0
  76. package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
  77. package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
  78. package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
  79. package/dist/modules/customers/ai-tools/types.js +10 -0
  80. package/dist/modules/customers/ai-tools/types.js.map +7 -0
  81. package/dist/modules/customers/ai-tools.js +20 -0
  82. package/dist/modules/customers/ai-tools.js.map +7 -0
  83. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
  84. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
  85. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
  86. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
  87. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
  88. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
  89. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
  90. package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
  91. package/dist/modules/customers/widgets/injection-table.js +26 -0
  92. package/dist/modules/customers/widgets/injection-table.js.map +7 -0
  93. package/dist/modules/inbox_ops/ai-tools.js +4 -0
  94. package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
  95. package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
  96. package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
  97. package/dist/modules/notifications/setup.js +13 -0
  98. package/dist/modules/notifications/setup.js.map +7 -0
  99. package/jest.config.cjs +1 -0
  100. package/jest.setup.ts +18 -0
  101. package/package.json +5 -3
  102. package/src/helpers/integration/api.ts +38 -16
  103. package/src/helpers/integration/auth.ts +13 -6
  104. package/src/modules/auth/commands/roles.ts +10 -12
  105. package/src/modules/catalog/AGENTS.md +11 -0
  106. package/src/modules/catalog/ai-agents-context.ts +239 -0
  107. package/src/modules/catalog/ai-agents.ts +525 -0
  108. package/src/modules/catalog/ai-tools/_shared.ts +487 -0
  109. package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
  110. package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
  111. package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
  112. package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
  113. package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
  114. package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
  115. package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
  116. package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
  117. package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
  118. package/src/modules/catalog/ai-tools/types.ts +81 -0
  119. package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
  120. package/src/modules/catalog/ai-tools.ts +78 -0
  121. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
  122. package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
  123. package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
  124. package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
  125. package/src/modules/catalog/events.ts +7 -4
  126. package/src/modules/catalog/i18n/de.json +17 -0
  127. package/src/modules/catalog/i18n/en.json +17 -0
  128. package/src/modules/catalog/i18n/es.json +17 -0
  129. package/src/modules/catalog/i18n/pl.json +17 -0
  130. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
  131. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
  132. package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
  133. package/src/modules/catalog/widgets/injection-table.ts +12 -0
  134. package/src/modules/customer_accounts/i18n/de.json +5 -0
  135. package/src/modules/customer_accounts/i18n/en.json +5 -0
  136. package/src/modules/customer_accounts/i18n/es.json +5 -0
  137. package/src/modules/customer_accounts/i18n/pl.json +5 -0
  138. package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
  139. package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
  140. package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
  141. package/src/modules/customers/AGENTS.md +13 -0
  142. package/src/modules/customers/ai-agents-context.ts +150 -0
  143. package/src/modules/customers/ai-agents.ts +355 -0
  144. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
  145. package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
  146. package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
  147. package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
  148. package/src/modules/customers/ai-tools/people-pack.ts +369 -0
  149. package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
  150. package/src/modules/customers/ai-tools/types.ts +76 -0
  151. package/src/modules/customers/ai-tools.ts +34 -0
  152. package/src/modules/customers/i18n/de.json +25 -0
  153. package/src/modules/customers/i18n/en.json +25 -0
  154. package/src/modules/customers/i18n/es.json +25 -0
  155. package/src/modules/customers/i18n/pl.json +25 -0
  156. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
  157. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
  158. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
  159. package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
  160. package/src/modules/customers/widgets/injection-table.ts +41 -0
  161. package/src/modules/inbox_ops/ai-tools.ts +4 -0
  162. package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
  163. package/src/modules/notifications/setup.ts +11 -0
@@ -0,0 +1,576 @@
1
+ import { z } from "zod";
2
+ import {
3
+ findOneWithDecryption,
4
+ findWithDecryption
5
+ } from "@open-mercato/shared/lib/encryption/find";
6
+ import { loadCustomFieldDefinitionIndex } from "@open-mercato/shared/lib/crud/custom-fields";
7
+ import { E } from "../../../generated/entities.ids.generated.js";
8
+ import {
9
+ createAiApiOperationRunner
10
+ } from "@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner";
11
+ import { CatalogProduct, CatalogProductPrice } from "../data/entities.js";
12
+ import { Attachment } from "@open-mercato/core/modules/attachments/data/entities";
13
+ import {
14
+ assertTenantScope
15
+ } from "./types.js";
16
+ function resolveEm(ctx) {
17
+ return ctx.container.resolve("em");
18
+ }
19
+ function buildScope(ctx, tenantId) {
20
+ return { tenantId, organizationId: ctx.organizationId };
21
+ }
22
+ function recordVersionFromUpdatedAt(updatedAt) {
23
+ if (!updatedAt) return null;
24
+ const value = updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
25
+ if (Number.isNaN(value.getTime())) return null;
26
+ return value.toISOString();
27
+ }
28
+ async function loadProductForScope(em, ctx, tenantId, productId) {
29
+ const where = { id: productId, tenantId, deletedAt: null };
30
+ if (ctx.organizationId) where.organizationId = ctx.organizationId;
31
+ const row = await findOneWithDecryption(
32
+ em,
33
+ CatalogProduct,
34
+ where,
35
+ void 0,
36
+ buildScope(ctx, tenantId)
37
+ );
38
+ if (!row || row.tenantId !== tenantId) return null;
39
+ if (ctx.organizationId && row.organizationId !== ctx.organizationId) return null;
40
+ return row;
41
+ }
42
+ function productSnapshot(row) {
43
+ return {
44
+ title: row.title ?? null,
45
+ subtitle: row.subtitle ?? null,
46
+ description: row.description ?? null,
47
+ sku: row.sku ?? null,
48
+ handle: row.handle ?? null,
49
+ isActive: row.isActive ?? null,
50
+ primaryCurrencyCode: row.primaryCurrencyCode ?? null
51
+ };
52
+ }
53
+ function productLabel(row) {
54
+ if (row.title && row.title.trim().length) return row.title;
55
+ if (row.sku && row.sku.trim().length) return row.sku;
56
+ return row.id;
57
+ }
58
+ const pricePatchSchema = z.object({
59
+ priceKindId: z.string().uuid().optional(),
60
+ currencyCode: z.string().trim().min(3).max(8).optional(),
61
+ amount: z.number().nonnegative()
62
+ }).strict();
63
+ async function validateProductPriceScope(em, tenantId, organizationId, productId, priceInput) {
64
+ if (priceInput.currencyCode) {
65
+ const candidates = await findWithDecryption(
66
+ em,
67
+ CatalogProductPrice,
68
+ { tenantId, product: productId },
69
+ void 0,
70
+ { tenantId, organizationId }
71
+ );
72
+ const seen = new Set(candidates.map((entry) => (entry.currencyCode ?? "").toUpperCase()));
73
+ if (seen.size > 0 && !seen.has(priceInput.currencyCode.toUpperCase())) {
74
+ return {
75
+ ok: false,
76
+ code: "currency_out_of_scope",
77
+ message: `Currency "${priceInput.currencyCode}" is not configured for product ${productId}.`
78
+ };
79
+ }
80
+ }
81
+ return { ok: true };
82
+ }
83
+ const updateProductInput = z.object({
84
+ productId: z.string().uuid().describe("Catalog product id (UUID)."),
85
+ title: z.string().trim().min(1).max(255).optional(),
86
+ subtitle: z.string().trim().max(255).nullable().optional(),
87
+ description: z.string().trim().max(4e3).nullable().optional(),
88
+ isActive: z.boolean().optional(),
89
+ price: pricePatchSchema.optional().describe("Optional price adjustment; must match an existing currency for the product.")
90
+ }).strict();
91
+ const updateProductTool = {
92
+ name: "catalog.update_product",
93
+ displayName: "Update product",
94
+ description: "Update a single catalog product (title, subtitle, description, isActive, optional price). Routes through the AI pending-action approval gate.",
95
+ inputSchema: updateProductInput,
96
+ requiredFeatures: ["catalog.products.manage"],
97
+ tags: ["write", "catalog", "merchandising"],
98
+ isMutation: true,
99
+ isBulk: false,
100
+ loadBeforeRecord: async (rawInput, ctx) => {
101
+ const { tenantId } = assertTenantScope(ctx);
102
+ const input = updateProductInput.parse(rawInput);
103
+ const em = resolveEm(ctx);
104
+ const row = await loadProductForScope(em, ctx, tenantId, input.productId);
105
+ if (!row) return null;
106
+ return {
107
+ recordId: row.id,
108
+ entityType: "catalog.product",
109
+ recordVersion: recordVersionFromUpdatedAt(row.updatedAt),
110
+ before: productSnapshot(row)
111
+ };
112
+ },
113
+ handler: async (rawInput, ctx) => {
114
+ const { tenantId } = assertTenantScope(ctx);
115
+ const input = updateProductInput.parse(rawInput);
116
+ const em = resolveEm(ctx);
117
+ const row = await loadProductForScope(em, ctx, tenantId, input.productId);
118
+ if (!row) {
119
+ throw new Error(`Product "${input.productId}" is not accessible to the caller.`);
120
+ }
121
+ const organizationId = row.organizationId;
122
+ const before = productSnapshot(row);
123
+ if (input.price) {
124
+ const check = await validateProductPriceScope(
125
+ em,
126
+ tenantId,
127
+ organizationId,
128
+ row.id,
129
+ input.price
130
+ );
131
+ if (!check.ok) {
132
+ const error = new Error(check.message);
133
+ error.code = check.code;
134
+ throw error;
135
+ }
136
+ }
137
+ const body = {
138
+ id: row.id,
139
+ tenantId,
140
+ organizationId
141
+ };
142
+ if (input.title !== void 0) body.title = input.title;
143
+ if (input.subtitle !== void 0) body.subtitle = input.subtitle;
144
+ if (input.description !== void 0) body.description = input.description;
145
+ if (input.isActive !== void 0) body.isActive = input.isActive;
146
+ const runner = createAiApiOperationRunner(ctx);
147
+ const response = await runner.run({
148
+ method: "PUT",
149
+ path: "/catalog/products",
150
+ body
151
+ });
152
+ if (!response.success) {
153
+ throw new Error(response.error ?? `Failed to update product "${row.id}"`);
154
+ }
155
+ const after = await loadProductForScope(em, ctx, tenantId, row.id);
156
+ return {
157
+ recordId: row.id,
158
+ commandName: "catalog.products.update",
159
+ before,
160
+ after: after ? productSnapshot(after) : null
161
+ };
162
+ }
163
+ };
164
+ const bulkUpdateProductRecordSchema = z.object({
165
+ recordId: z.string().uuid(),
166
+ title: z.string().trim().min(1).max(255).optional(),
167
+ subtitle: z.string().trim().max(255).nullable().optional(),
168
+ description: z.string().trim().max(4e3).nullable().optional(),
169
+ isActive: z.boolean().optional(),
170
+ price: pricePatchSchema.optional()
171
+ }).strict();
172
+ const bulkUpdateProductsInput = z.object({
173
+ records: z.array(bulkUpdateProductRecordSchema).min(1).max(50).describe("One entry per product to update. Max 50 rows per batch.")
174
+ }).strict();
175
+ const bulkUpdateProductsTool = {
176
+ name: "catalog.bulk_update_products",
177
+ displayName: "Bulk update products",
178
+ description: "Update several catalog products in a single approval. Emits ONE pending action whose records[] contains one diff per product. Stale-version or cross-tenant rows are collected in failedRecords[] without aborting the remaining writes.",
179
+ inputSchema: bulkUpdateProductsInput,
180
+ requiredFeatures: ["catalog.products.manage"],
181
+ tags: ["write", "catalog", "merchandising", "bulk"],
182
+ isMutation: true,
183
+ isBulk: true,
184
+ loadBeforeRecords: async (rawInput, ctx) => {
185
+ const { tenantId } = assertTenantScope(ctx);
186
+ const input = bulkUpdateProductsInput.parse(rawInput);
187
+ const em = resolveEm(ctx);
188
+ const rows = [];
189
+ for (const entry of input.records) {
190
+ const product = await loadProductForScope(em, ctx, tenantId, entry.recordId);
191
+ if (!product) continue;
192
+ rows.push({
193
+ recordId: product.id,
194
+ entityType: "catalog.product",
195
+ label: productLabel(product),
196
+ recordVersion: recordVersionFromUpdatedAt(product.updatedAt),
197
+ before: productSnapshot(product)
198
+ });
199
+ }
200
+ return rows;
201
+ },
202
+ handler: async (rawInput, ctx) => {
203
+ const { tenantId } = assertTenantScope(ctx);
204
+ const input = bulkUpdateProductsInput.parse(rawInput);
205
+ const em = resolveEm(ctx);
206
+ const runner = createAiApiOperationRunner(ctx);
207
+ const results = [];
208
+ const failedRecordIds = [];
209
+ for (const entry of input.records) {
210
+ const product = await loadProductForScope(em, ctx, tenantId, entry.recordId);
211
+ if (!product) {
212
+ failedRecordIds.push(entry.recordId);
213
+ results.push({
214
+ recordId: entry.recordId,
215
+ status: "skipped",
216
+ before: null,
217
+ after: null,
218
+ error: { code: "record_not_found", message: "Product is not accessible to the caller." }
219
+ });
220
+ continue;
221
+ }
222
+ const organizationId = product.organizationId;
223
+ const before = productSnapshot(product);
224
+ if (entry.price) {
225
+ const check = await validateProductPriceScope(em, tenantId, organizationId, product.id, entry.price);
226
+ if (!check.ok) {
227
+ failedRecordIds.push(product.id);
228
+ results.push({
229
+ recordId: product.id,
230
+ status: "failed",
231
+ before,
232
+ after: null,
233
+ error: { code: check.code, message: check.message }
234
+ });
235
+ continue;
236
+ }
237
+ }
238
+ const body = {
239
+ id: product.id,
240
+ tenantId,
241
+ organizationId
242
+ };
243
+ if (entry.title !== void 0) body.title = entry.title;
244
+ if (entry.subtitle !== void 0) body.subtitle = entry.subtitle;
245
+ if (entry.description !== void 0) body.description = entry.description;
246
+ if (entry.isActive !== void 0) body.isActive = entry.isActive;
247
+ try {
248
+ const response = await runner.run({
249
+ method: "PUT",
250
+ path: "/catalog/products",
251
+ body
252
+ });
253
+ if (!response.success) {
254
+ const code = typeof response.details?.code === "string" ? response.details.code : "command_failed";
255
+ failedRecordIds.push(product.id);
256
+ results.push({
257
+ recordId: product.id,
258
+ status: "failed",
259
+ before,
260
+ after: null,
261
+ error: { code, message: response.error ?? "API operation failed" }
262
+ });
263
+ continue;
264
+ }
265
+ const after = await loadProductForScope(em, ctx, tenantId, product.id);
266
+ results.push({
267
+ recordId: product.id,
268
+ status: "updated",
269
+ before,
270
+ after: after ? productSnapshot(after) : null
271
+ });
272
+ } catch (error) {
273
+ failedRecordIds.push(product.id);
274
+ const message = error instanceof Error ? error.message : String(error);
275
+ const code = error?.code ?? "command_failed";
276
+ results.push({
277
+ recordId: product.id,
278
+ status: "failed",
279
+ before,
280
+ after: null,
281
+ error: { code, message }
282
+ });
283
+ }
284
+ }
285
+ const everyFailed = results.length > 0 && results.every((entry) => entry.status !== "updated");
286
+ return {
287
+ commandName: "catalog.products.update",
288
+ records: results,
289
+ failedRecordIds,
290
+ error: everyFailed ? { code: "all_records_failed", message: "No records were updated." } : void 0
291
+ };
292
+ }
293
+ };
294
+ const attributeExtractionRecordSchema = z.object({
295
+ recordId: z.string().uuid(),
296
+ attributes: z.record(z.string(), z.unknown()).describe("Attribute key \u2192 value map produced by catalog.extract_attributes_from_description.")
297
+ }).strict();
298
+ const applyAttributeExtractionInput = z.object({
299
+ records: z.array(attributeExtractionRecordSchema).min(1).max(50).describe("One extraction payload per product. Max 50 rows per batch.")
300
+ }).strict();
301
+ async function resolveAttributeKeyIndex(ctx, tenantId) {
302
+ try {
303
+ const em = resolveEm(ctx);
304
+ const index = await loadCustomFieldDefinitionIndex({
305
+ em,
306
+ entityIds: E.catalog.catalog_product,
307
+ tenantId,
308
+ organizationIds: ctx.organizationId ? [ctx.organizationId] : null
309
+ });
310
+ const keys = /* @__PURE__ */ new Set();
311
+ if (index && typeof index.keys === "function") {
312
+ for (const key of index.keys()) {
313
+ keys.add(String(key));
314
+ }
315
+ }
316
+ return keys;
317
+ } catch {
318
+ return /* @__PURE__ */ new Set();
319
+ }
320
+ }
321
+ const applyAttributeExtractionTool = {
322
+ name: "catalog.apply_attribute_extraction",
323
+ displayName: "Apply attribute extraction",
324
+ description: "Persist the structured attribute output of catalog.extract_attributes_from_description. Re-validates every attribute key against the CURRENT catalog.product custom-field schema before the pending action is created \u2014 schema drift trips attribute_not_in_schema.",
325
+ inputSchema: applyAttributeExtractionInput,
326
+ requiredFeatures: ["catalog.products.manage"],
327
+ tags: ["write", "catalog", "merchandising", "attributes"],
328
+ isMutation: true,
329
+ isBulk: true,
330
+ loadBeforeRecords: async (rawInput, ctx) => {
331
+ const { tenantId } = assertTenantScope(ctx);
332
+ const input = applyAttributeExtractionInput.parse(rawInput);
333
+ const em = resolveEm(ctx);
334
+ const rows = [];
335
+ for (const entry of input.records) {
336
+ const product = await loadProductForScope(em, ctx, tenantId, entry.recordId);
337
+ if (!product) continue;
338
+ rows.push({
339
+ recordId: product.id,
340
+ entityType: "catalog.product",
341
+ label: productLabel(product),
342
+ recordVersion: recordVersionFromUpdatedAt(product.updatedAt),
343
+ before: { attributes: {} }
344
+ });
345
+ }
346
+ return rows;
347
+ },
348
+ handler: async (rawInput, ctx) => {
349
+ const { tenantId } = assertTenantScope(ctx);
350
+ const input = applyAttributeExtractionInput.parse(rawInput);
351
+ const em = resolveEm(ctx);
352
+ const runner = createAiApiOperationRunner(ctx);
353
+ const knownKeys = await resolveAttributeKeyIndex(ctx, tenantId);
354
+ const results = [];
355
+ const failedRecordIds = [];
356
+ for (const entry of input.records) {
357
+ const product = await loadProductForScope(em, ctx, tenantId, entry.recordId);
358
+ if (!product) {
359
+ failedRecordIds.push(entry.recordId);
360
+ results.push({
361
+ recordId: entry.recordId,
362
+ status: "skipped",
363
+ before: null,
364
+ after: null,
365
+ error: { code: "record_not_found", message: "Product is not accessible to the caller." }
366
+ });
367
+ continue;
368
+ }
369
+ const attributeKeys = Object.keys(entry.attributes ?? {});
370
+ const unknownKey = knownKeys.size > 0 ? attributeKeys.find((key) => !knownKeys.has(key)) : void 0;
371
+ if (unknownKey) {
372
+ failedRecordIds.push(product.id);
373
+ results.push({
374
+ recordId: product.id,
375
+ status: "failed",
376
+ before: { attributes: {} },
377
+ after: null,
378
+ error: {
379
+ code: "attribute_not_in_schema",
380
+ message: `Attribute "${unknownKey}" is not part of the current catalog.product schema.`
381
+ }
382
+ });
383
+ continue;
384
+ }
385
+ const organizationId = product.organizationId;
386
+ const customFields = {};
387
+ for (const [key, value] of Object.entries(entry.attributes ?? {})) {
388
+ customFields[key] = value;
389
+ }
390
+ const body = {
391
+ id: product.id,
392
+ tenantId,
393
+ organizationId,
394
+ customFields
395
+ };
396
+ try {
397
+ const response = await runner.run({
398
+ method: "PUT",
399
+ path: "/catalog/products",
400
+ body
401
+ });
402
+ if (!response.success) {
403
+ const code = typeof response.details?.code === "string" ? response.details.code : "command_failed";
404
+ failedRecordIds.push(product.id);
405
+ results.push({
406
+ recordId: product.id,
407
+ status: "failed",
408
+ before: { attributes: {} },
409
+ after: null,
410
+ error: { code, message: response.error ?? "API operation failed" }
411
+ });
412
+ continue;
413
+ }
414
+ results.push({
415
+ recordId: product.id,
416
+ status: "updated",
417
+ before: { attributes: {} },
418
+ after: { attributes: entry.attributes }
419
+ });
420
+ } catch (error) {
421
+ failedRecordIds.push(product.id);
422
+ const message = error instanceof Error ? error.message : String(error);
423
+ const code = error?.code ?? "command_failed";
424
+ results.push({
425
+ recordId: product.id,
426
+ status: "failed",
427
+ before: { attributes: {} },
428
+ after: null,
429
+ error: { code, message }
430
+ });
431
+ }
432
+ }
433
+ const everyFailed = results.length > 0 && results.every((entry) => entry.status !== "updated");
434
+ return {
435
+ commandName: "catalog.products.update",
436
+ records: results,
437
+ failedRecordIds,
438
+ error: everyFailed ? { code: "all_records_failed", message: "No records were updated." } : void 0
439
+ };
440
+ }
441
+ };
442
+ const mediaUpdateRecordSchema = z.object({
443
+ mediaId: z.string().uuid(),
444
+ altText: z.string().trim().max(500).optional(),
445
+ caption: z.string().trim().max(1e3).optional()
446
+ }).strict();
447
+ const updateProductMediaDescriptionsInput = z.object({
448
+ mediaUpdates: z.array(mediaUpdateRecordSchema).min(1).max(100).describe("One media record descriptor per attachment id. Exactly one entry is allowed for single-record writes; many entries for bulk writes.")
449
+ }).strict();
450
+ async function loadProductMediaForScope(em, ctx, tenantId, mediaId) {
451
+ const where = {
452
+ id: mediaId,
453
+ tenantId,
454
+ entityId: E.catalog.catalog_product
455
+ };
456
+ if (ctx.organizationId) where.organizationId = ctx.organizationId;
457
+ const row = await findOneWithDecryption(
458
+ em,
459
+ Attachment,
460
+ where,
461
+ void 0,
462
+ buildScope(ctx, tenantId)
463
+ );
464
+ if (!row || row.tenantId !== tenantId) return null;
465
+ if (ctx.organizationId && row.organizationId !== ctx.organizationId) return null;
466
+ return row;
467
+ }
468
+ function mediaSnapshot(row) {
469
+ const metadata = row.storageMetadata ?? {};
470
+ return {
471
+ altText: typeof metadata?.altText === "string" ? metadata.altText : null,
472
+ caption: typeof metadata?.caption === "string" ? metadata.caption : null
473
+ };
474
+ }
475
+ function mediaLabel(row) {
476
+ if (row.fileName && row.fileName.trim().length) return row.fileName;
477
+ return row.id;
478
+ }
479
+ const updateProductMediaDescriptionsTool = {
480
+ name: "catalog.update_product_media_descriptions",
481
+ displayName: "Update product media descriptions",
482
+ description: "Update altText / caption on one or many product media attachments in a single approval. Emits one pending action with records[] carrying per-media diffs. Accepts one entry for single-record writes or many for bulk writes \u2014 always routes through loadBeforeRecords to guarantee schema-consistent preview.",
483
+ inputSchema: updateProductMediaDescriptionsInput,
484
+ requiredFeatures: ["catalog.products.manage"],
485
+ tags: ["write", "catalog", "merchandising", "media"],
486
+ isMutation: true,
487
+ isBulk: true,
488
+ loadBeforeRecords: async (rawInput, ctx) => {
489
+ const { tenantId } = assertTenantScope(ctx);
490
+ const input = updateProductMediaDescriptionsInput.parse(rawInput);
491
+ const em = resolveEm(ctx);
492
+ const rows = [];
493
+ for (const entry of input.mediaUpdates) {
494
+ const media = await loadProductMediaForScope(em, ctx, tenantId, entry.mediaId);
495
+ if (!media) continue;
496
+ rows.push({
497
+ recordId: media.id,
498
+ entityType: "catalog.product_media",
499
+ label: mediaLabel(media),
500
+ recordVersion: recordVersionFromUpdatedAt(media.createdAt ?? null),
501
+ before: mediaSnapshot(media)
502
+ });
503
+ }
504
+ return rows;
505
+ },
506
+ handler: async (rawInput, ctx) => {
507
+ const { tenantId } = assertTenantScope(ctx);
508
+ const input = updateProductMediaDescriptionsInput.parse(rawInput);
509
+ const em = resolveEm(ctx);
510
+ const results = [];
511
+ const failedRecordIds = [];
512
+ const touched = [];
513
+ for (const entry of input.mediaUpdates) {
514
+ const media = await loadProductMediaForScope(em, ctx, tenantId, entry.mediaId);
515
+ if (!media) {
516
+ failedRecordIds.push(entry.mediaId);
517
+ results.push({
518
+ recordId: entry.mediaId,
519
+ status: "skipped",
520
+ before: null,
521
+ after: null,
522
+ error: { code: "record_not_found", message: "Media is not accessible to the caller." }
523
+ });
524
+ continue;
525
+ }
526
+ const before = mediaSnapshot(media);
527
+ touched.push({
528
+ row: media,
529
+ patch: {
530
+ ...entry.altText !== void 0 ? { altText: entry.altText } : {},
531
+ ...entry.caption !== void 0 ? { caption: entry.caption } : {}
532
+ },
533
+ before
534
+ });
535
+ }
536
+ if (touched.length > 0) {
537
+ for (const { row, patch } of touched) {
538
+ const next = { ...row.storageMetadata ?? {} };
539
+ if (patch.altText !== void 0) next.altText = patch.altText;
540
+ if (patch.caption !== void 0) next.caption = patch.caption;
541
+ row.storageMetadata = next;
542
+ }
543
+ await em.flush();
544
+ for (const { row, patch, before } of touched) {
545
+ results.push({
546
+ recordId: row.id,
547
+ status: "updated",
548
+ before,
549
+ after: {
550
+ altText: patch.altText ?? before.altText ?? null,
551
+ caption: patch.caption ?? before.caption ?? null
552
+ }
553
+ });
554
+ }
555
+ }
556
+ const everyFailed = results.length > 0 && results.every((entry) => entry.status !== "updated");
557
+ return {
558
+ commandName: "catalog.products.media.update_descriptions",
559
+ records: results,
560
+ failedRecordIds,
561
+ error: everyFailed ? { code: "all_records_failed", message: "No media records were updated." } : void 0
562
+ };
563
+ }
564
+ };
565
+ const mutationAiTools = [
566
+ updateProductTool,
567
+ bulkUpdateProductsTool,
568
+ applyAttributeExtractionTool,
569
+ updateProductMediaDescriptionsTool
570
+ ];
571
+ var mutation_pack_default = mutationAiTools;
572
+ export {
573
+ mutation_pack_default as default,
574
+ mutationAiTools
575
+ };
576
+ //# sourceMappingURL=mutation-pack.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/catalog/ai-tools/mutation-pack.ts"],
4
+ "sourcesContent": ["/**\n * Catalog D18 mutation tool pack (Phase 3 WS-C, Step 5.14).\n *\n * Ships the four mutation tools the `catalog.merchandising_assistant`\n * (Step 4.9) whitelists under the pending-action approval contract:\n *\n * 1. `catalog.update_product` \u2014 single-record write (isBulk=false).\n * 2. `catalog.bulk_update_products` \u2014 batch write (isBulk=true). One\n * pending action per batch; per-record versions carried in\n * `records[]` so Step 5.8's confirm route can reject stale rows\n * individually without aborting the batch.\n * 3. `catalog.apply_attribute_extraction` \u2014 batch write that persists\n * the structured output of `catalog.extract_attributes_from_description`\n * (Step 3.12) against the CURRENT attribute schema. Schema drift is\n * caught at two points: (a) the tool's pre-submit validator rejects\n * attribute keys that do not resolve to a custom-field definition,\n * and (b) the Step 5.8 re-check loop calls `loadBeforeRecords` again\n * before confirm.\n * 4. `catalog.update_product_media_descriptions` \u2014 batch write that\n * updates altText / caption on product media attachments via the\n * attachment's `storageMetadata` envelope. No attachments command\n * exists yet, so the handler performs a guarded direct EM flush.\n * The writes are scalar only (no intermediate queries on the same EM)\n * so `em.flush()` in a single phase is safe and matches the spec's\n * \"single pending-action per batch\" contract.\n *\n * All four tools:\n * - Are tenant + organization scoped through `findOneWithDecryption` /\n * `findWithDecryption` before any write.\n * - Use `z.object(...).strict()` on their input schemas (spec \u00A77 rule:\n * reject hallucinated attribute names / unknown fields).\n * - Preserve per-record result shape when only some records fail:\n * handler returns `{ records: [{ recordId, status, before, after, error? }], failedRecordIds }`\n * so Step 5.8's executor can populate `failedRecords[]` on the\n * `AiPendingAction` row without the caller treating a partial failure\n * as a batch failure.\n * - Delegate the authoritative write to an existing command\n * (`catalog.products.update`) where possible, so all downstream side\n * effects (audit log, `catalog.product.updated` event, query index\n * refresh, notifications) stay identical to a direct API write. The\n * media-description tool is the one exception: it updates\n * `attachments.storage_metadata` directly because no attachments\n * command exists. Tenant + organization scope are re-checked inside\n * the direct write.\n *\n * BC: additive only. `CatalogAiToolDefinition` grows optional\n * `loadBeforeRecord` / `loadBeforeRecords` / `isBulk` fields that default\n * to undefined on every pre-5.14 tool. No new feature ids, no DB\n * migrations, no event-id changes. Mutation-policy override at the\n * tenant level is still the only lever that unlocks writes \u2014 agent\n * `readOnly: true` stays unchanged (Step 5.13 discipline).\n */\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport {\n findOneWithDecryption,\n findWithDecryption,\n} from '@open-mercato/shared/lib/encryption/find'\nimport { loadCustomFieldDefinitionIndex } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n createAiApiOperationRunner,\n type AiToolExecutionContext,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport { CatalogProduct, CatalogProductPrice } from '../data/entities'\nimport { Attachment } from '@open-mercato/core/modules/attachments/data/entities'\nimport {\n assertTenantScope,\n type CatalogAiToolDefinition,\n type CatalogToolContext,\n type CatalogToolLoadBeforeRecord,\n type CatalogToolLoadBeforeSingleRecord,\n} from './types'\n\nfunction resolveEm(ctx: CatalogToolContext): EntityManager {\n return ctx.container.resolve<EntityManager>('em')\n}\n\nfunction buildScope(ctx: CatalogToolContext, tenantId: string) {\n return { tenantId, organizationId: ctx.organizationId }\n}\n\nfunction recordVersionFromUpdatedAt(updatedAt: Date | null | undefined): string | null {\n if (!updatedAt) return null\n const value = updatedAt instanceof Date ? updatedAt : new Date(updatedAt)\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString()\n}\n\nasync function loadProductForScope(\n em: EntityManager,\n ctx: CatalogToolContext,\n tenantId: string,\n productId: string,\n): Promise<CatalogProduct | null> {\n const where: Record<string, unknown> = { id: productId, tenantId, deletedAt: null }\n if (ctx.organizationId) where.organizationId = ctx.organizationId\n const row = await findOneWithDecryption<CatalogProduct>(\n em,\n CatalogProduct,\n where as any,\n undefined,\n buildScope(ctx, tenantId),\n )\n if (!row || row.tenantId !== tenantId) return null\n if (ctx.organizationId && row.organizationId !== ctx.organizationId) return null\n return row\n}\n\nfunction productSnapshot(row: CatalogProduct): Record<string, unknown> {\n return {\n title: row.title ?? null,\n subtitle: row.subtitle ?? null,\n description: row.description ?? null,\n sku: row.sku ?? null,\n handle: row.handle ?? null,\n isActive: row.isActive ?? null,\n primaryCurrencyCode: row.primaryCurrencyCode ?? null,\n }\n}\n\nfunction productLabel(row: CatalogProduct): string {\n if (row.title && row.title.trim().length) return row.title\n if (row.sku && row.sku.trim().length) return row.sku\n return row.id\n}\n\n/* -------------------------------------------------------------------------- */\n/* Price pre-submit validation */\n/* -------------------------------------------------------------------------- */\n\nconst pricePatchSchema = z\n .object({\n priceKindId: z.string().uuid().optional(),\n currencyCode: z.string().trim().min(3).max(8).optional(),\n amount: z.number().nonnegative(),\n })\n .strict()\n\nasync function validateProductPriceScope(\n em: EntityManager,\n tenantId: string,\n organizationId: string,\n productId: string,\n priceInput: z.infer<typeof pricePatchSchema>,\n): Promise<{ ok: true } | { ok: false; code: string; message: string }> {\n if (priceInput.currencyCode) {\n const candidates = await findWithDecryption<CatalogProductPrice>(\n em,\n CatalogProductPrice,\n { tenantId, product: productId } as any,\n undefined,\n { tenantId, organizationId },\n )\n const seen = new Set<string>(candidates.map((entry) => (entry.currencyCode ?? '').toUpperCase()))\n if (seen.size > 0 && !seen.has(priceInput.currencyCode.toUpperCase())) {\n return {\n ok: false,\n code: 'currency_out_of_scope',\n message: `Currency \"${priceInput.currencyCode}\" is not configured for product ${productId}.`,\n }\n }\n }\n return { ok: true }\n}\n\n/* -------------------------------------------------------------------------- */\n/* catalog.update_product (single-record) */\n/* -------------------------------------------------------------------------- */\n\nconst updateProductInput = z\n .object({\n productId: z.string().uuid().describe('Catalog product id (UUID).'),\n title: z.string().trim().min(1).max(255).optional(),\n subtitle: z.string().trim().max(255).nullable().optional(),\n description: z.string().trim().max(4000).nullable().optional(),\n isActive: z.boolean().optional(),\n price: pricePatchSchema.optional().describe('Optional price adjustment; must match an existing currency for the product.'),\n })\n .strict()\n\ntype UpdateProductInput = z.infer<typeof updateProductInput>\n\nconst updateProductTool: CatalogAiToolDefinition = {\n name: 'catalog.update_product',\n displayName: 'Update product',\n description:\n 'Update a single catalog product (title, subtitle, description, isActive, optional price). Routes through the AI pending-action approval gate.',\n inputSchema: updateProductInput as z.ZodType<unknown>,\n requiredFeatures: ['catalog.products.manage'],\n tags: ['write', 'catalog', 'merchandising'],\n isMutation: true,\n isBulk: false,\n loadBeforeRecord: async (\n rawInput,\n ctx,\n ): Promise<CatalogToolLoadBeforeSingleRecord | null> => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateProductInput = updateProductInput.parse(rawInput)\n const em = resolveEm(ctx)\n const row = await loadProductForScope(em, ctx, tenantId, input.productId)\n if (!row) return null\n return {\n recordId: row.id,\n entityType: 'catalog.product',\n recordVersion: recordVersionFromUpdatedAt(row.updatedAt),\n before: productSnapshot(row),\n }\n },\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateProductInput = updateProductInput.parse(rawInput)\n const em = resolveEm(ctx)\n const row = await loadProductForScope(em, ctx, tenantId, input.productId)\n if (!row) {\n throw new Error(`Product \"${input.productId}\" is not accessible to the caller.`)\n }\n const organizationId = row.organizationId\n const before = productSnapshot(row)\n\n if (input.price) {\n const check = await validateProductPriceScope(\n em,\n tenantId,\n organizationId,\n row.id,\n input.price,\n )\n if (!check.ok) {\n const error = new Error(check.message) as Error & { code?: string }\n error.code = check.code\n throw error\n }\n }\n\n const body: Record<string, unknown> = {\n id: row.id,\n tenantId,\n organizationId,\n }\n if (input.title !== undefined) body.title = input.title\n if (input.subtitle !== undefined) body.subtitle = input.subtitle\n if (input.description !== undefined) body.description = input.description\n if (input.isActive !== undefined) body.isActive = input.isActive\n\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n const response = await runner.run({\n method: 'PUT',\n path: '/catalog/products',\n body,\n })\n if (!response.success) {\n throw new Error(response.error ?? `Failed to update product \"${row.id}\"`)\n }\n\n const after = await loadProductForScope(em, ctx, tenantId, row.id)\n return {\n recordId: row.id,\n commandName: 'catalog.products.update',\n before,\n after: after ? productSnapshot(after) : null,\n }\n },\n}\n\n/* -------------------------------------------------------------------------- */\n/* catalog.bulk_update_products (batch) */\n/* -------------------------------------------------------------------------- */\n\nconst bulkUpdateProductRecordSchema = z\n .object({\n recordId: z.string().uuid(),\n title: z.string().trim().min(1).max(255).optional(),\n subtitle: z.string().trim().max(255).nullable().optional(),\n description: z.string().trim().max(4000).nullable().optional(),\n isActive: z.boolean().optional(),\n price: pricePatchSchema.optional(),\n })\n .strict()\n\nconst bulkUpdateProductsInput = z\n .object({\n records: z\n .array(bulkUpdateProductRecordSchema)\n .min(1)\n .max(50)\n .describe('One entry per product to update. Max 50 rows per batch.'),\n })\n .strict()\n\ntype BulkUpdateProductsInput = z.infer<typeof bulkUpdateProductsInput>\ntype BulkUpdateProductPatch = z.infer<typeof bulkUpdateProductRecordSchema>\n\ntype BulkRecordResult = {\n recordId: string\n status: 'updated' | 'skipped' | 'failed'\n before: Record<string, unknown> | null\n after: Record<string, unknown> | null\n error?: { code: string; message: string }\n}\n\nconst bulkUpdateProductsTool: CatalogAiToolDefinition = {\n name: 'catalog.bulk_update_products',\n displayName: 'Bulk update products',\n description:\n 'Update several catalog products in a single approval. Emits ONE pending action whose records[] contains one diff per product. Stale-version or cross-tenant rows are collected in failedRecords[] without aborting the remaining writes.',\n inputSchema: bulkUpdateProductsInput as z.ZodType<unknown>,\n requiredFeatures: ['catalog.products.manage'],\n tags: ['write', 'catalog', 'merchandising', 'bulk'],\n isMutation: true,\n isBulk: true,\n loadBeforeRecords: async (rawInput, ctx): Promise<CatalogToolLoadBeforeRecord[]> => {\n const { tenantId } = assertTenantScope(ctx)\n const input: BulkUpdateProductsInput = bulkUpdateProductsInput.parse(rawInput)\n const em = resolveEm(ctx)\n const rows: CatalogToolLoadBeforeRecord[] = []\n for (const entry of input.records) {\n const product = await loadProductForScope(em, ctx, tenantId, entry.recordId)\n if (!product) continue\n rows.push({\n recordId: product.id,\n entityType: 'catalog.product',\n label: productLabel(product),\n recordVersion: recordVersionFromUpdatedAt(product.updatedAt),\n before: productSnapshot(product),\n })\n }\n return rows\n },\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input: BulkUpdateProductsInput = bulkUpdateProductsInput.parse(rawInput)\n const em = resolveEm(ctx)\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n const results: BulkRecordResult[] = []\n const failedRecordIds: string[] = []\n for (const entry of input.records) {\n const product = await loadProductForScope(em, ctx, tenantId, entry.recordId)\n if (!product) {\n failedRecordIds.push(entry.recordId)\n results.push({\n recordId: entry.recordId,\n status: 'skipped',\n before: null,\n after: null,\n error: { code: 'record_not_found', message: 'Product is not accessible to the caller.' },\n })\n continue\n }\n const organizationId = product.organizationId\n const before = productSnapshot(product)\n if (entry.price) {\n const check = await validateProductPriceScope(em, tenantId, organizationId, product.id, entry.price)\n if (!check.ok) {\n failedRecordIds.push(product.id)\n results.push({\n recordId: product.id,\n status: 'failed',\n before,\n after: null,\n error: { code: check.code, message: check.message },\n })\n continue\n }\n }\n const body: Record<string, unknown> = {\n id: product.id,\n tenantId,\n organizationId,\n }\n if (entry.title !== undefined) body.title = entry.title\n if (entry.subtitle !== undefined) body.subtitle = entry.subtitle\n if (entry.description !== undefined) body.description = entry.description\n if (entry.isActive !== undefined) body.isActive = entry.isActive\n try {\n const response = await runner.run({\n method: 'PUT',\n path: '/catalog/products',\n body,\n })\n if (!response.success) {\n const code =\n typeof (response.details as { code?: unknown } | undefined)?.code === 'string'\n ? ((response.details as { code: string }).code)\n : 'command_failed'\n failedRecordIds.push(product.id)\n results.push({\n recordId: product.id,\n status: 'failed',\n before,\n after: null,\n error: { code, message: response.error ?? 'API operation failed' },\n })\n continue\n }\n const after = await loadProductForScope(em, ctx, tenantId, product.id)\n results.push({\n recordId: product.id,\n status: 'updated',\n before,\n after: after ? productSnapshot(after) : null,\n })\n } catch (error) {\n failedRecordIds.push(product.id)\n const message = error instanceof Error ? error.message : String(error)\n const code = ((error as { code?: unknown })?.code as string | undefined) ?? 'command_failed'\n results.push({\n recordId: product.id,\n status: 'failed',\n before,\n after: null,\n error: { code, message },\n })\n }\n }\n const everyFailed = results.length > 0 && results.every((entry) => entry.status !== 'updated')\n return {\n commandName: 'catalog.products.update',\n records: results,\n failedRecordIds,\n error: everyFailed ? { code: 'all_records_failed', message: 'No records were updated.' } : undefined,\n }\n },\n}\n\n/* -------------------------------------------------------------------------- */\n/* catalog.apply_attribute_extraction (batch) */\n/* -------------------------------------------------------------------------- */\n\nconst attributeExtractionRecordSchema = z\n .object({\n recordId: z.string().uuid(),\n attributes: z.record(z.string(), z.unknown()).describe('Attribute key \u2192 value map produced by catalog.extract_attributes_from_description.'),\n })\n .strict()\n\nconst applyAttributeExtractionInput = z\n .object({\n records: z\n .array(attributeExtractionRecordSchema)\n .min(1)\n .max(50)\n .describe('One extraction payload per product. Max 50 rows per batch.'),\n })\n .strict()\n\ntype ApplyAttributeExtractionInput = z.infer<typeof applyAttributeExtractionInput>\n\nasync function resolveAttributeKeyIndex(\n ctx: CatalogToolContext,\n tenantId: string,\n): Promise<Set<string>> {\n try {\n const em = resolveEm(ctx)\n const index = await loadCustomFieldDefinitionIndex({\n em,\n entityIds: E.catalog.catalog_product as string,\n tenantId,\n organizationIds: ctx.organizationId ? [ctx.organizationId] : null,\n })\n const keys = new Set<string>()\n if (index && typeof (index as any).keys === 'function') {\n for (const key of (index as any).keys() as Iterable<string>) {\n keys.add(String(key))\n }\n }\n return keys\n } catch {\n return new Set<string>()\n }\n}\n\nconst applyAttributeExtractionTool: CatalogAiToolDefinition = {\n name: 'catalog.apply_attribute_extraction',\n displayName: 'Apply attribute extraction',\n description:\n 'Persist the structured attribute output of catalog.extract_attributes_from_description. Re-validates every attribute key against the CURRENT catalog.product custom-field schema before the pending action is created \u2014 schema drift trips attribute_not_in_schema.',\n inputSchema: applyAttributeExtractionInput as z.ZodType<unknown>,\n requiredFeatures: ['catalog.products.manage'],\n tags: ['write', 'catalog', 'merchandising', 'attributes'],\n isMutation: true,\n isBulk: true,\n loadBeforeRecords: async (rawInput, ctx): Promise<CatalogToolLoadBeforeRecord[]> => {\n const { tenantId } = assertTenantScope(ctx)\n const input: ApplyAttributeExtractionInput = applyAttributeExtractionInput.parse(rawInput)\n const em = resolveEm(ctx)\n const rows: CatalogToolLoadBeforeRecord[] = []\n for (const entry of input.records) {\n const product = await loadProductForScope(em, ctx, tenantId, entry.recordId)\n if (!product) continue\n rows.push({\n recordId: product.id,\n entityType: 'catalog.product',\n label: productLabel(product),\n recordVersion: recordVersionFromUpdatedAt(product.updatedAt),\n before: { attributes: {} },\n })\n }\n return rows\n },\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input: ApplyAttributeExtractionInput = applyAttributeExtractionInput.parse(rawInput)\n const em = resolveEm(ctx)\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n const knownKeys = await resolveAttributeKeyIndex(ctx, tenantId)\n const results: BulkRecordResult[] = []\n const failedRecordIds: string[] = []\n for (const entry of input.records) {\n const product = await loadProductForScope(em, ctx, tenantId, entry.recordId)\n if (!product) {\n failedRecordIds.push(entry.recordId)\n results.push({\n recordId: entry.recordId,\n status: 'skipped',\n before: null,\n after: null,\n error: { code: 'record_not_found', message: 'Product is not accessible to the caller.' },\n })\n continue\n }\n const attributeKeys = Object.keys(entry.attributes ?? {})\n const unknownKey = knownKeys.size > 0\n ? attributeKeys.find((key) => !knownKeys.has(key))\n : undefined\n if (unknownKey) {\n failedRecordIds.push(product.id)\n results.push({\n recordId: product.id,\n status: 'failed',\n before: { attributes: {} },\n after: null,\n error: {\n code: 'attribute_not_in_schema',\n message: `Attribute \"${unknownKey}\" is not part of the current catalog.product schema.`,\n },\n })\n continue\n }\n const organizationId = product.organizationId\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(entry.attributes ?? {})) {\n customFields[key] = value\n }\n const body: Record<string, unknown> = {\n id: product.id,\n tenantId,\n organizationId,\n customFields,\n }\n try {\n const response = await runner.run({\n method: 'PUT',\n path: '/catalog/products',\n body,\n })\n if (!response.success) {\n const code =\n typeof (response.details as { code?: unknown } | undefined)?.code === 'string'\n ? ((response.details as { code: string }).code)\n : 'command_failed'\n failedRecordIds.push(product.id)\n results.push({\n recordId: product.id,\n status: 'failed',\n before: { attributes: {} },\n after: null,\n error: { code, message: response.error ?? 'API operation failed' },\n })\n continue\n }\n results.push({\n recordId: product.id,\n status: 'updated',\n before: { attributes: {} },\n after: { attributes: entry.attributes },\n })\n } catch (error) {\n failedRecordIds.push(product.id)\n const message = error instanceof Error ? error.message : String(error)\n const code = ((error as { code?: unknown })?.code as string | undefined) ?? 'command_failed'\n results.push({\n recordId: product.id,\n status: 'failed',\n before: { attributes: {} },\n after: null,\n error: { code, message },\n })\n }\n }\n const everyFailed = results.length > 0 && results.every((entry) => entry.status !== 'updated')\n return {\n commandName: 'catalog.products.update',\n records: results,\n failedRecordIds,\n error: everyFailed ? { code: 'all_records_failed', message: 'No records were updated.' } : undefined,\n }\n },\n}\n\n/* -------------------------------------------------------------------------- */\n/* catalog.update_product_media_descriptions (one-or-many media updates) */\n/* -------------------------------------------------------------------------- */\n\nconst mediaUpdateRecordSchema = z\n .object({\n mediaId: z.string().uuid(),\n altText: z.string().trim().max(500).optional(),\n caption: z.string().trim().max(1000).optional(),\n })\n .strict()\n\nconst updateProductMediaDescriptionsInput = z\n .object({\n mediaUpdates: z\n .array(mediaUpdateRecordSchema)\n .min(1)\n .max(100)\n .describe('One media record descriptor per attachment id. Exactly one entry is allowed for single-record writes; many entries for bulk writes.'),\n })\n .strict()\n\ntype UpdateProductMediaDescriptionsInput = z.infer<typeof updateProductMediaDescriptionsInput>\n\nasync function loadProductMediaForScope(\n em: EntityManager,\n ctx: CatalogToolContext,\n tenantId: string,\n mediaId: string,\n): Promise<Attachment | null> {\n const where: Record<string, unknown> = {\n id: mediaId,\n tenantId,\n entityId: E.catalog.catalog_product,\n }\n if (ctx.organizationId) where.organizationId = ctx.organizationId\n const row = await findOneWithDecryption<Attachment>(\n em,\n Attachment,\n where as any,\n undefined,\n buildScope(ctx, tenantId),\n )\n if (!row || row.tenantId !== tenantId) return null\n if (ctx.organizationId && row.organizationId !== ctx.organizationId) return null\n return row\n}\n\nfunction mediaSnapshot(row: Attachment): Record<string, unknown> {\n const metadata = row.storageMetadata ?? {}\n return {\n altText: typeof metadata?.altText === 'string' ? metadata.altText : null,\n caption: typeof metadata?.caption === 'string' ? metadata.caption : null,\n }\n}\n\nfunction mediaLabel(row: Attachment): string {\n if (row.fileName && row.fileName.trim().length) return row.fileName\n return row.id\n}\n\nconst updateProductMediaDescriptionsTool: CatalogAiToolDefinition = {\n name: 'catalog.update_product_media_descriptions',\n displayName: 'Update product media descriptions',\n description:\n 'Update altText / caption on one or many product media attachments in a single approval. Emits one pending action with records[] carrying per-media diffs. Accepts one entry for single-record writes or many for bulk writes \u2014 always routes through loadBeforeRecords to guarantee schema-consistent preview.',\n inputSchema: updateProductMediaDescriptionsInput as z.ZodType<unknown>,\n requiredFeatures: ['catalog.products.manage'],\n tags: ['write', 'catalog', 'merchandising', 'media'],\n isMutation: true,\n isBulk: true,\n loadBeforeRecords: async (rawInput, ctx): Promise<CatalogToolLoadBeforeRecord[]> => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateProductMediaDescriptionsInput =\n updateProductMediaDescriptionsInput.parse(rawInput)\n const em = resolveEm(ctx)\n const rows: CatalogToolLoadBeforeRecord[] = []\n for (const entry of input.mediaUpdates) {\n const media = await loadProductMediaForScope(em, ctx, tenantId, entry.mediaId)\n if (!media) continue\n rows.push({\n recordId: media.id,\n entityType: 'catalog.product_media',\n label: mediaLabel(media),\n recordVersion: recordVersionFromUpdatedAt(media.createdAt ?? null),\n before: mediaSnapshot(media),\n })\n }\n return rows\n },\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateProductMediaDescriptionsInput =\n updateProductMediaDescriptionsInput.parse(rawInput)\n const em = resolveEm(ctx)\n const results: BulkRecordResult[] = []\n const failedRecordIds: string[] = []\n const touched: Array<{ row: Attachment; patch: { altText?: string; caption?: string }; before: Record<string, unknown> }> = []\n for (const entry of input.mediaUpdates) {\n const media = await loadProductMediaForScope(em, ctx, tenantId, entry.mediaId)\n if (!media) {\n failedRecordIds.push(entry.mediaId)\n results.push({\n recordId: entry.mediaId,\n status: 'skipped',\n before: null,\n after: null,\n error: { code: 'record_not_found', message: 'Media is not accessible to the caller.' },\n })\n continue\n }\n const before = mediaSnapshot(media)\n touched.push({\n row: media,\n patch: {\n ...(entry.altText !== undefined ? { altText: entry.altText } : {}),\n ...(entry.caption !== undefined ? { caption: entry.caption } : {}),\n },\n before,\n })\n }\n if (touched.length > 0) {\n for (const { row, patch } of touched) {\n const next = { ...(row.storageMetadata ?? {}) } as Record<string, unknown>\n if (patch.altText !== undefined) next.altText = patch.altText\n if (patch.caption !== undefined) next.caption = patch.caption\n row.storageMetadata = next\n }\n await em.flush()\n for (const { row, patch, before } of touched) {\n results.push({\n recordId: row.id,\n status: 'updated',\n before,\n after: {\n altText: patch.altText ?? (before as any).altText ?? null,\n caption: patch.caption ?? (before as any).caption ?? null,\n },\n })\n }\n }\n const everyFailed = results.length > 0 && results.every((entry) => entry.status !== 'updated')\n return {\n commandName: 'catalog.products.media.update_descriptions',\n records: results,\n failedRecordIds,\n error: everyFailed ? { code: 'all_records_failed', message: 'No media records were updated.' } : undefined,\n }\n },\n}\n\n/* -------------------------------------------------------------------------- */\n\nexport const mutationAiTools: CatalogAiToolDefinition[] = [\n updateProductTool,\n bulkUpdateProductsTool,\n applyAttributeExtractionTool,\n updateProductMediaDescriptionsTool,\n]\n\nexport default mutationAiTools\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }