@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,208 @@
1
+ import { z } from "zod";
2
+ import { defineApiBackedAiTool } from "@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool";
3
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { CatalogProductPrice } from "../data/entities.js";
5
+ import { assertTenantScope } from "./types.js";
6
+ import { listPriceKindsCore } from "./_shared.js";
7
+ function resolveEm(ctx) {
8
+ return ctx.container.resolve("em");
9
+ }
10
+ function buildScope(ctx, tenantId) {
11
+ return { tenantId, organizationId: ctx.organizationId };
12
+ }
13
+ const listPricesInput = z.object({
14
+ productId: z.string().uuid().optional().describe("Restrict to prices attached to this product."),
15
+ variantId: z.string().uuid().optional().describe("Restrict to prices attached to this variant."),
16
+ priceKindId: z.string().uuid().optional().describe("Restrict to this price kind."),
17
+ limit: z.number().int().min(1).max(100).optional().describe("Max rows (default 50, max 100)."),
18
+ offset: z.number().int().min(0).optional().describe("Rows to skip (default 0).")
19
+ }).passthrough();
20
+ const listPricesTool = defineApiBackedAiTool({
21
+ name: "catalog.list_prices",
22
+ displayName: "List prices",
23
+ description: "List catalog prices (base + offer-scoped) for the caller tenant + organization. Filters: product, variant, or price kind.",
24
+ inputSchema: listPricesInput,
25
+ requiredFeatures: ["catalog.products.view"],
26
+ toOperation: (input, ctx) => {
27
+ assertTenantScope(ctx);
28
+ const limit = input.limit ?? 50;
29
+ const offset = input.offset ?? 0;
30
+ const page = Math.floor(offset / limit) + 1;
31
+ const query = {
32
+ page,
33
+ pageSize: limit
34
+ };
35
+ if (input.productId) query.productId = input.productId;
36
+ if (input.variantId) query.variantId = input.variantId;
37
+ if (input.priceKindId) query.priceKindId = input.priceKindId;
38
+ const operation = {
39
+ method: "GET",
40
+ path: "/catalog/prices",
41
+ query
42
+ };
43
+ return operation;
44
+ },
45
+ mapResponse: (response, input) => {
46
+ const limit = input.limit ?? 50;
47
+ const offset = input.offset ?? 0;
48
+ const data = response.data ?? {};
49
+ const rawItems = Array.isArray(data.items) ? data.items : [];
50
+ return {
51
+ items: rawItems.map((row) => {
52
+ const startsAtRaw = row.starts_at ?? row.startsAt ?? null;
53
+ const startsAt = startsAtRaw ? new Date(String(startsAtRaw)).toISOString() : null;
54
+ const endsAtRaw = row.ends_at ?? row.endsAt ?? null;
55
+ const endsAt = endsAtRaw ? new Date(String(endsAtRaw)).toISOString() : null;
56
+ const createdAtRaw = row.created_at ?? row.createdAt ?? null;
57
+ const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
58
+ return {
59
+ id: row.id,
60
+ priceKindId: row.price_kind_id ?? row.priceKindId ?? null,
61
+ productId: row.product_id ?? row.productId ?? null,
62
+ variantId: row.variant_id ?? row.variantId ?? null,
63
+ offerId: row.offer_id ?? row.offerId ?? null,
64
+ currencyCode: row.currency_code ?? row.currencyCode ?? null,
65
+ kind: row.kind ?? null,
66
+ minQuantity: row.min_quantity ?? row.minQuantity ?? null,
67
+ maxQuantity: row.max_quantity ?? row.maxQuantity ?? null,
68
+ unitPriceNet: row.unit_price_net ?? row.unitPriceNet ?? null,
69
+ unitPriceGross: row.unit_price_gross ?? row.unitPriceGross ?? null,
70
+ taxRate: row.tax_rate ?? row.taxRate ?? null,
71
+ taxAmount: row.tax_amount ?? row.taxAmount ?? null,
72
+ channelId: row.channel_id ?? row.channelId ?? null,
73
+ userId: row.user_id ?? row.userId ?? null,
74
+ userGroupId: row.user_group_id ?? row.userGroupId ?? null,
75
+ customerId: row.customer_id ?? row.customerId ?? null,
76
+ customerGroupId: row.customer_group_id ?? row.customerGroupId ?? null,
77
+ startsAt,
78
+ endsAt,
79
+ organizationId: row.organization_id ?? row.organizationId ?? null,
80
+ tenantId: row.tenant_id ?? row.tenantId ?? null,
81
+ createdAt
82
+ };
83
+ }),
84
+ total: typeof data.total === "number" ? data.total : 0,
85
+ limit,
86
+ offset
87
+ };
88
+ }
89
+ });
90
+ const listPriceKindsInput = z.object({
91
+ limit: z.number().int().min(1).max(100).optional().describe("Max rows (default 50, max 100)."),
92
+ offset: z.number().int().min(0).optional().describe("Rows to skip (default 0).")
93
+ }).passthrough();
94
+ const listPriceKindsTool = {
95
+ name: "catalog.list_price_kinds_base",
96
+ displayName: "List price kinds (base)",
97
+ description: "Enumerate the tenant price kinds. Base coverage tool \u2014 Step 3.11 (D18) owns `catalog.list_price_kinds` verbatim; this tool uses a distinct name to avoid collision.",
98
+ inputSchema: listPriceKindsInput,
99
+ requiredFeatures: ["catalog.settings.manage"],
100
+ tags: ["read", "catalog"],
101
+ handler: async (rawInput, ctx) => {
102
+ const { tenantId } = assertTenantScope(ctx);
103
+ const input = listPriceKindsInput.parse(rawInput);
104
+ return listPriceKindsCore(ctx, input, tenantId);
105
+ }
106
+ };
107
+ const listOffersInput = z.object({
108
+ productId: z.string().uuid().optional().describe("Restrict to offers for this product."),
109
+ variantId: z.string().uuid().optional().describe("Restrict to offers whose prices are variant-scoped."),
110
+ active: z.boolean().optional().describe("When true, only active (non-archived) offers are returned."),
111
+ limit: z.number().int().min(1).max(100).optional().describe("Max rows (default 50, max 100)."),
112
+ offset: z.number().int().min(0).optional().describe("Rows to skip (default 0).")
113
+ }).passthrough();
114
+ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
115
+ async function resolveOfferIdsForVariant(ctx, tenantId, variantId) {
116
+ const em = resolveEm(ctx);
117
+ const priceWhere = { tenantId, variant: variantId };
118
+ if (ctx.organizationId) priceWhere.organizationId = ctx.organizationId;
119
+ const prices = await findWithDecryption(
120
+ em,
121
+ CatalogProductPrice,
122
+ priceWhere,
123
+ void 0,
124
+ buildScope(ctx, tenantId)
125
+ );
126
+ const offerIds = prices.map((price) => price.offer).map((offer) => offer && typeof offer === "object" ? offer.id : offer).filter((value) => typeof value === "string" && value.length > 0);
127
+ return Array.from(new Set(offerIds));
128
+ }
129
+ const listOffersTool = defineApiBackedAiTool({
130
+ name: "catalog.list_offers",
131
+ displayName: "List offers",
132
+ description: "List catalog offers for the caller tenant + organization, optionally narrowed to a product (or a variant via its prices).",
133
+ inputSchema: listOffersInput,
134
+ requiredFeatures: ["catalog.products.view"],
135
+ toOperation: async (input, ctx) => {
136
+ const { tenantId } = assertTenantScope(ctx);
137
+ const limit = input.limit ?? 50;
138
+ const offset = input.offset ?? 0;
139
+ const page = Math.floor(offset / limit) + 1;
140
+ const query = {
141
+ page,
142
+ pageSize: limit
143
+ };
144
+ if (input.productId) query.productId = input.productId;
145
+ if (input.active === true) query.isActive = "true";
146
+ if (input.variantId) {
147
+ const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId);
148
+ if (offerIds.length === 0) {
149
+ query.id = NIL_UUID;
150
+ } else if (offerIds.length === 1) {
151
+ query.id = offerIds[0];
152
+ }
153
+ }
154
+ const operation = {
155
+ method: "GET",
156
+ path: "/catalog/offers",
157
+ query
158
+ };
159
+ return operation;
160
+ },
161
+ mapResponse: async (response, input, ctx) => {
162
+ const limit = input.limit ?? 50;
163
+ const offset = input.offset ?? 0;
164
+ const data = response.data ?? {};
165
+ let rawItems = Array.isArray(data.items) ? data.items : [];
166
+ let total = typeof data.total === "number" ? data.total : 0;
167
+ if (input.variantId) {
168
+ const { tenantId } = assertTenantScope(ctx);
169
+ const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId);
170
+ const offerIdSet = new Set(offerIds);
171
+ rawItems = rawItems.filter((row) => typeof row.id === "string" && offerIdSet.has(row.id));
172
+ total = rawItems.length;
173
+ }
174
+ return {
175
+ items: rawItems.map((row) => {
176
+ const createdAtRaw = row.created_at ?? row.createdAt ?? null;
177
+ const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
178
+ return {
179
+ id: row.id,
180
+ title: row.title ?? "",
181
+ description: row.description ?? null,
182
+ channelId: row.channel_id ?? row.channelId ?? null,
183
+ productId: row.product_id ?? row.productId ?? null,
184
+ defaultMediaId: row.default_media_id ?? row.defaultMediaId ?? null,
185
+ defaultMediaUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,
186
+ isActive: !!(row.is_active ?? row.isActive),
187
+ organizationId: row.organization_id ?? row.organizationId ?? null,
188
+ tenantId: row.tenant_id ?? row.tenantId ?? null,
189
+ createdAt
190
+ };
191
+ }),
192
+ total,
193
+ limit,
194
+ offset
195
+ };
196
+ }
197
+ });
198
+ const pricesOffersAiTools = [
199
+ listPricesTool,
200
+ listPriceKindsTool,
201
+ listOffersTool
202
+ ];
203
+ var prices_offers_pack_default = pricesOffersAiTools;
204
+ export {
205
+ prices_offers_pack_default as default,
206
+ pricesOffersAiTools
207
+ };
208
+ //# sourceMappingURL=prices-offers-pack.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/catalog/ai-tools/prices-offers-pack.ts"],
4
+ "sourcesContent": ["/**\n * `catalog.list_prices`, `catalog.list_price_kinds_base`, `catalog.list_offers`\n * (Phase 1 WS-C, Step 3.10).\n *\n * Read-only enumeration of prices (base + offer-bound), price kinds, and\n * offers for the caller tenant + organization. Mutation tools land in Step\n * 5.14 under the pending-action contract.\n *\n * `catalog.list_price_kinds_base` uses a distinct name on purpose \u2014 Step\n * 3.11 (D18) will own `catalog.list_price_kinds` verbatim; we keep both\n * names available so the D18 tool can layer merchandising-specific shape\n * over the base enumerator.\n *\n * Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:\n * `catalog.list_prices` and `catalog.list_offers` are now API-backed wrappers\n * over `GET /api/catalog/prices` and `GET /api/catalog/offers`. Tool names,\n * schemas, requiredFeatures, and output shapes are unchanged. The offers\n * route does not expose a `variantId` filter; the AI input is pre-resolved\n * via `CatalogProductPrice` to the matching offer ids and threaded through\n * the route's `id` filter (or post-filtered when more than one matches),\n * mirroring Phase 3a's `companyId` \u2192 `ids` trick for `customers.list_people`.\n */\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'\nimport type {\n AiApiOperationRequest,\n AiToolExecutionContext,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CatalogProductPrice } from '../data/entities'\nimport { assertTenantScope, type CatalogAiToolDefinition, type CatalogToolContext } from './types'\nimport { listPriceKindsCore } from './_shared'\n\nfunction resolveEm(ctx: CatalogToolContext | AiToolExecutionContext): EntityManager {\n return ctx.container.resolve<EntityManager>('em')\n}\n\nfunction buildScope(ctx: CatalogToolContext | AiToolExecutionContext, tenantId: string) {\n return { tenantId, organizationId: ctx.organizationId }\n}\n\nconst listPricesInput = z\n .object({\n productId: z.string().uuid().optional().describe('Restrict to prices attached to this product.'),\n variantId: z.string().uuid().optional().describe('Restrict to prices attached to this variant.'),\n priceKindId: z.string().uuid().optional().describe('Restrict to this price kind.'),\n limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),\n })\n .passthrough()\n\ntype ListPricesInput = z.infer<typeof listPricesInput>\n\ntype ListPricesApiItem = {\n id?: string\n product_id?: string | null\n productId?: string | null\n variant_id?: string | null\n variantId?: string | null\n offer_id?: string | null\n offerId?: string | null\n price_kind_id?: string | null\n priceKindId?: string | null\n currency_code?: string | null\n currencyCode?: string | null\n kind?: string | null\n min_quantity?: number | null\n minQuantity?: number | null\n max_quantity?: number | null\n maxQuantity?: number | null\n unit_price_net?: string | number | null\n unitPriceNet?: string | number | null\n unit_price_gross?: string | number | null\n unitPriceGross?: string | number | null\n tax_rate?: string | number | null\n taxRate?: string | number | null\n tax_amount?: string | number | null\n taxAmount?: string | number | null\n channel_id?: string | null\n channelId?: string | null\n user_id?: string | null\n userId?: string | null\n user_group_id?: string | null\n userGroupId?: string | null\n customer_id?: string | null\n customerId?: string | null\n customer_group_id?: string | null\n customerGroupId?: string | null\n starts_at?: string | null\n startsAt?: string | null\n ends_at?: string | null\n endsAt?: string | null\n organization_id?: string | null\n organizationId?: string | null\n tenant_id?: string | null\n tenantId?: string | null\n created_at?: string | null\n createdAt?: string | null\n}\n\ntype ListPricesApiResponse = {\n items?: ListPricesApiItem[]\n total?: number\n}\n\ntype ListPricesOutput = {\n items: Array<Record<string, unknown>>\n total: number\n limit: number\n offset: number\n}\n\nconst listPricesTool = defineApiBackedAiTool<\n ListPricesInput,\n ListPricesApiResponse,\n ListPricesOutput\n>({\n name: 'catalog.list_prices',\n displayName: 'List prices',\n description:\n 'List catalog prices (base + offer-scoped) for the caller tenant + organization. Filters: product, variant, or price kind.',\n inputSchema: listPricesInput,\n requiredFeatures: ['catalog.products.view'],\n toOperation: (input, ctx) => {\n assertTenantScope(ctx as unknown as CatalogToolContext)\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const query: Record<string, string | number | boolean | null | undefined> = {\n page,\n pageSize: limit,\n }\n if (input.productId) query.productId = input.productId\n if (input.variantId) query.variantId = input.variantId\n if (input.priceKindId) query.priceKindId = input.priceKindId\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: '/catalog/prices',\n query,\n }\n return operation\n },\n mapResponse: (response, input) => {\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const data = (response.data ?? {}) as ListPricesApiResponse\n const rawItems: ListPricesApiItem[] = Array.isArray(data.items) ? data.items : []\n return {\n items: rawItems.map((row) => {\n const startsAtRaw = row.starts_at ?? row.startsAt ?? null\n const startsAt = startsAtRaw ? new Date(String(startsAtRaw)).toISOString() : null\n const endsAtRaw = row.ends_at ?? row.endsAt ?? null\n const endsAt = endsAtRaw ? new Date(String(endsAtRaw)).toISOString() : null\n const createdAtRaw = row.created_at ?? row.createdAt ?? null\n const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null\n return {\n id: row.id,\n priceKindId: row.price_kind_id ?? row.priceKindId ?? null,\n productId: row.product_id ?? row.productId ?? null,\n variantId: row.variant_id ?? row.variantId ?? null,\n offerId: row.offer_id ?? row.offerId ?? null,\n currencyCode: row.currency_code ?? row.currencyCode ?? null,\n kind: row.kind ?? null,\n minQuantity: row.min_quantity ?? row.minQuantity ?? null,\n maxQuantity: row.max_quantity ?? row.maxQuantity ?? null,\n unitPriceNet: row.unit_price_net ?? row.unitPriceNet ?? null,\n unitPriceGross: row.unit_price_gross ?? row.unitPriceGross ?? null,\n taxRate: row.tax_rate ?? row.taxRate ?? null,\n taxAmount: row.tax_amount ?? row.taxAmount ?? null,\n channelId: row.channel_id ?? row.channelId ?? null,\n userId: row.user_id ?? row.userId ?? null,\n userGroupId: row.user_group_id ?? row.userGroupId ?? null,\n customerId: row.customer_id ?? row.customerId ?? null,\n customerGroupId: row.customer_group_id ?? row.customerGroupId ?? null,\n startsAt,\n endsAt,\n organizationId: row.organization_id ?? row.organizationId ?? null,\n tenantId: row.tenant_id ?? row.tenantId ?? null,\n createdAt,\n }\n }),\n total: typeof data.total === 'number' ? data.total : 0,\n limit,\n offset,\n }\n },\n}) as unknown as CatalogAiToolDefinition\n\nconst listPriceKindsInput = z\n .object({\n limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),\n })\n .passthrough()\n\nconst listPriceKindsTool: CatalogAiToolDefinition = {\n name: 'catalog.list_price_kinds_base',\n displayName: 'List price kinds (base)',\n description:\n 'Enumerate the tenant price kinds. Base coverage tool \u2014 Step 3.11 (D18) owns `catalog.list_price_kinds` verbatim; this tool uses a distinct name to avoid collision.',\n inputSchema: listPriceKindsInput,\n requiredFeatures: ['catalog.settings.manage'],\n tags: ['read', 'catalog'],\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input = listPriceKindsInput.parse(rawInput)\n // Shared helper; Step 3.11 `catalog.list_price_kinds` uses the same core\n // so both tools cannot drift.\n return listPriceKindsCore(ctx, input, tenantId)\n },\n}\n\nconst listOffersInput = z\n .object({\n productId: z.string().uuid().optional().describe('Restrict to offers for this product.'),\n variantId: z.string().uuid().optional().describe('Restrict to offers whose prices are variant-scoped.'),\n active: z.boolean().optional().describe('When true, only active (non-archived) offers are returned.'),\n limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),\n })\n .passthrough()\n\nconst NIL_UUID = '00000000-0000-0000-0000-000000000000'\n\ntype ListOffersInput = z.infer<typeof listOffersInput>\n\ntype ListOffersApiItem = {\n id?: string\n product_id?: string | null\n productId?: string | null\n channel_id?: string | null\n channelId?: string | null\n title?: string | null\n description?: string | null\n default_media_id?: string | null\n defaultMediaId?: string | null\n default_media_url?: string | null\n defaultMediaUrl?: string | null\n is_active?: boolean | null\n isActive?: boolean | null\n organization_id?: string | null\n organizationId?: string | null\n tenant_id?: string | null\n tenantId?: string | null\n created_at?: string | null\n createdAt?: string | null\n}\n\ntype ListOffersApiResponse = {\n items?: ListOffersApiItem[]\n total?: number\n}\n\ntype ListOffersOutput = {\n items: Array<Record<string, unknown>>\n total: number\n limit: number\n offset: number\n}\n\nasync function resolveOfferIdsForVariant(\n ctx: AiToolExecutionContext | CatalogToolContext,\n tenantId: string,\n variantId: string,\n): Promise<string[]> {\n const em = resolveEm(ctx)\n const priceWhere: Record<string, unknown> = { tenantId, variant: variantId }\n if (ctx.organizationId) priceWhere.organizationId = ctx.organizationId\n const prices = await findWithDecryption<CatalogProductPrice>(\n em,\n CatalogProductPrice,\n priceWhere as any,\n undefined,\n buildScope(ctx, tenantId),\n )\n const offerIds = prices\n .map((price) => (price as any).offer)\n .map((offer) => (offer && typeof offer === 'object' ? offer.id : offer))\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n return Array.from(new Set(offerIds))\n}\n\nconst listOffersTool = defineApiBackedAiTool<\n ListOffersInput,\n ListOffersApiResponse,\n ListOffersOutput\n>({\n name: 'catalog.list_offers',\n displayName: 'List offers',\n description:\n 'List catalog offers for the caller tenant + organization, optionally narrowed to a product (or a variant via its prices).',\n inputSchema: listOffersInput,\n requiredFeatures: ['catalog.products.view'],\n toOperation: async (input, ctx) => {\n const { tenantId } = assertTenantScope(ctx as unknown as CatalogToolContext)\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const query: Record<string, string | number | boolean | null | undefined> = {\n page,\n pageSize: limit,\n }\n if (input.productId) query.productId = input.productId\n if (input.active === true) query.isActive = 'true'\n\n if (input.variantId) {\n const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId)\n if (offerIds.length === 0) {\n // Empty match \u2014 feed a non-existent uuid so the route returns an\n // empty page without us bypassing the API.\n query.id = NIL_UUID\n } else if (offerIds.length === 1) {\n query.id = offerIds[0]\n }\n // For >1 offer ids the route's single-id filter cannot narrow; the\n // mapper post-filters the unfiltered response by the resolved ids.\n }\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: '/catalog/offers',\n query,\n }\n return operation\n },\n mapResponse: async (response, input, ctx) => {\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const data = (response.data ?? {}) as ListOffersApiResponse\n let rawItems: ListOffersApiItem[] = Array.isArray(data.items) ? data.items : []\n let total = typeof data.total === 'number' ? data.total : 0\n\n if (input.variantId) {\n const { tenantId } = assertTenantScope(ctx as unknown as CatalogToolContext)\n const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId)\n const offerIdSet = new Set(offerIds)\n rawItems = rawItems.filter((row) => typeof row.id === 'string' && offerIdSet.has(row.id))\n total = rawItems.length\n }\n\n return {\n items: rawItems.map((row) => {\n const createdAtRaw = row.created_at ?? row.createdAt ?? null\n const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null\n return {\n id: row.id,\n title: row.title ?? '',\n description: row.description ?? null,\n channelId: row.channel_id ?? row.channelId ?? null,\n productId: row.product_id ?? row.productId ?? null,\n defaultMediaId: row.default_media_id ?? row.defaultMediaId ?? null,\n defaultMediaUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,\n isActive: !!(row.is_active ?? row.isActive),\n organizationId: row.organization_id ?? row.organizationId ?? null,\n tenantId: row.tenant_id ?? row.tenantId ?? null,\n createdAt,\n }\n }),\n total,\n limit,\n offset,\n }\n },\n}) as unknown as CatalogAiToolDefinition\n\nexport const pricesOffersAiTools: CatalogAiToolDefinition[] = [\n listPricesTool,\n listPriceKindsTool,\n listOffersTool,\n]\n\nexport default pricesOffersAiTools\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,298 @@
1
+ import { z } from "zod";
2
+ import { defineApiBackedAiTool } from "@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool";
3
+ import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
5
+ import { E } from "../../../generated/entities.ids.generated.js";
6
+ import { Attachment } from "@open-mercato/core/modules/attachments/data/entities";
7
+ import {
8
+ CatalogProduct,
9
+ CatalogProductCategoryAssignment,
10
+ CatalogProductTagAssignment,
11
+ CatalogProductVariant,
12
+ CatalogProductPrice,
13
+ CatalogProductUnitConversion
14
+ } from "../data/entities.js";
15
+ import { assertTenantScope } 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
+ const listProductsInput = z.object({
23
+ q: z.string().trim().min(1).optional().describe("Optional search text matched against title / subtitle / sku / handle."),
24
+ limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return (default 50, max 100)."),
25
+ offset: z.number().int().min(0).optional().describe("Number of rows to skip (default 0)."),
26
+ categoryId: z.string().uuid().optional().describe("Restrict to products assigned to this catalog category."),
27
+ tagIds: z.array(z.string().uuid()).optional().describe("Restrict to products carrying at least one of these tag ids."),
28
+ active: z.boolean().optional().describe("When true, only active (not archived) products are returned.")
29
+ }).passthrough();
30
+ const listProductsTool = defineApiBackedAiTool({
31
+ name: "catalog.list_products",
32
+ displayName: "List products",
33
+ description: "Search / list catalog products for the caller tenant + organization. Returns { items, total, limit, offset }.",
34
+ inputSchema: listProductsInput,
35
+ requiredFeatures: ["catalog.products.view"],
36
+ toOperation: (input, ctx) => {
37
+ assertTenantScope(ctx);
38
+ const limit = input.limit ?? 50;
39
+ const offset = input.offset ?? 0;
40
+ const page = Math.floor(offset / limit) + 1;
41
+ const query = {
42
+ page,
43
+ pageSize: limit
44
+ };
45
+ if (input.q?.trim()) query.search = input.q.trim();
46
+ if (input.categoryId) query.categoryIds = input.categoryId;
47
+ if (input.tagIds && input.tagIds.length > 0) query.tagIds = input.tagIds.join(",");
48
+ if (input.active === true) query.isActive = "true";
49
+ const operation = {
50
+ method: "GET",
51
+ path: "/catalog/products",
52
+ query
53
+ };
54
+ return operation;
55
+ },
56
+ mapResponse: (response, input) => {
57
+ const limit = input.limit ?? 50;
58
+ const offset = input.offset ?? 0;
59
+ const data = response.data ?? {};
60
+ const rawItems = Array.isArray(data.items) ? data.items : [];
61
+ return {
62
+ items: rawItems.map((row) => {
63
+ const createdAtRaw = row.created_at ?? row.createdAt ?? null;
64
+ const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
65
+ const updatedAtRaw = row.updated_at ?? row.updatedAt ?? null;
66
+ const updatedAt = updatedAtRaw ? new Date(String(updatedAtRaw)).toISOString() : null;
67
+ return {
68
+ id: row.id,
69
+ title: row.title ?? null,
70
+ subtitle: row.subtitle ?? null,
71
+ sku: row.sku ?? null,
72
+ handle: row.handle ?? null,
73
+ productType: row.product_type ?? row.productType ?? null,
74
+ statusEntryId: row.status_entry_id ?? row.statusEntryId ?? null,
75
+ primaryCurrencyCode: row.primary_currency_code ?? row.primaryCurrencyCode ?? null,
76
+ defaultMediaId: row.default_media_id ?? row.defaultMediaId ?? null,
77
+ defaultMediaUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,
78
+ imageUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,
79
+ isActive: !!(row.is_active ?? row.isActive),
80
+ isConfigurable: !!(row.is_configurable ?? row.isConfigurable),
81
+ organizationId: row.organization_id ?? row.organizationId ?? null,
82
+ tenantId: row.tenant_id ?? row.tenantId ?? null,
83
+ createdAt,
84
+ updatedAt
85
+ };
86
+ }),
87
+ total: typeof data.total === "number" ? data.total : 0,
88
+ limit,
89
+ offset
90
+ };
91
+ }
92
+ });
93
+ const getProductInput = z.object({
94
+ productId: z.string().uuid().describe("Catalog product id (UUID)."),
95
+ includeRelated: z.boolean().optional().describe(
96
+ "When true, include categories, tags, variants, prices (base + offers), media (metadata only), unit conversions, and custom fields (each related list capped at 100)."
97
+ )
98
+ });
99
+ const getProductTool = {
100
+ name: "catalog.get_product",
101
+ displayName: "Get product",
102
+ description: "Fetch a catalog product by id with core fields and (optionally) categories, tags, variants, prices, media metadata, unit conversions, and custom fields. Returns { found: false } when the record is outside tenant/org scope or missing.",
103
+ inputSchema: getProductInput,
104
+ requiredFeatures: ["catalog.products.view"],
105
+ tags: ["read", "catalog"],
106
+ handler: async (rawInput, ctx) => {
107
+ const { tenantId } = assertTenantScope(ctx);
108
+ const input = getProductInput.parse(rawInput);
109
+ const em = resolveEm(ctx);
110
+ const where = {
111
+ id: input.productId,
112
+ tenantId,
113
+ deletedAt: null
114
+ };
115
+ if (ctx.organizationId) where.organizationId = ctx.organizationId;
116
+ const product = await findOneWithDecryption(
117
+ em,
118
+ CatalogProduct,
119
+ where,
120
+ void 0,
121
+ buildScope(ctx, tenantId)
122
+ );
123
+ if (!product || product.tenantId !== tenantId) {
124
+ return { found: false, productId: input.productId };
125
+ }
126
+ let related = null;
127
+ let customFields = {};
128
+ if (input.includeRelated) {
129
+ const scope = buildScope(ctx, tenantId);
130
+ const [
131
+ categoryAssignments,
132
+ tagAssignments,
133
+ variants,
134
+ prices,
135
+ mediaAttachments,
136
+ unitConversions,
137
+ customFieldValues
138
+ ] = await Promise.all([
139
+ findWithDecryption(
140
+ em,
141
+ CatalogProductCategoryAssignment,
142
+ { tenantId, product: product.id },
143
+ { limit: 100, populate: ["category"] },
144
+ scope
145
+ ),
146
+ findWithDecryption(
147
+ em,
148
+ CatalogProductTagAssignment,
149
+ { tenantId, product: product.id },
150
+ { limit: 100, populate: ["tag"] },
151
+ scope
152
+ ),
153
+ findWithDecryption(
154
+ em,
155
+ CatalogProductVariant,
156
+ { tenantId, product: product.id, deletedAt: null },
157
+ { limit: 100, orderBy: { createdAt: "asc" } },
158
+ scope
159
+ ),
160
+ findWithDecryption(
161
+ em,
162
+ CatalogProductPrice,
163
+ { tenantId, product: product.id },
164
+ { limit: 100, orderBy: { createdAt: "asc" } },
165
+ scope
166
+ ),
167
+ findWithDecryption(
168
+ em,
169
+ Attachment,
170
+ { tenantId, entityId: E.catalog.catalog_product, recordId: product.id },
171
+ { limit: 100, orderBy: { createdAt: "asc" } },
172
+ scope
173
+ ),
174
+ findWithDecryption(
175
+ em,
176
+ CatalogProductUnitConversion,
177
+ { tenantId, product: product.id, deletedAt: null },
178
+ { limit: 100, orderBy: { sortOrder: "asc", createdAt: "asc" } },
179
+ scope
180
+ ),
181
+ loadCustomFieldValues({
182
+ em,
183
+ entityId: E.catalog.catalog_product,
184
+ recordIds: [product.id],
185
+ tenantIdByRecord: { [product.id]: product.tenantId ?? null },
186
+ organizationIdByRecord: { [product.id]: product.organizationId ?? null },
187
+ tenantFallbacks: [product.tenantId ?? tenantId].filter((value) => !!value)
188
+ })
189
+ ]);
190
+ customFields = customFieldValues[product.id] ?? {};
191
+ related = {
192
+ categories: categoryAssignments.map((assignment) => {
193
+ const category = assignment.category;
194
+ if (!category || typeof category === "string") {
195
+ const fallbackId = typeof category === "string" ? category : null;
196
+ return fallbackId ? { id: fallbackId, name: null, slug: null } : null;
197
+ }
198
+ return {
199
+ id: category.id,
200
+ name: category.name ?? null,
201
+ slug: category.slug ?? null
202
+ };
203
+ }).filter((value) => value !== null),
204
+ tags: tagAssignments.map((assignment) => {
205
+ const tag = assignment.tag;
206
+ if (!tag || typeof tag === "string") return null;
207
+ return { id: tag.id, label: tag.label, slug: tag.slug };
208
+ }).filter((value) => value !== null),
209
+ variants: variants.map((variant) => ({
210
+ id: variant.id,
211
+ name: variant.name ?? null,
212
+ sku: variant.sku ?? null,
213
+ barcode: variant.barcode ?? null,
214
+ optionValues: variant.optionValues ?? null,
215
+ defaultMediaId: variant.defaultMediaId ?? null,
216
+ defaultMediaUrl: variant.defaultMediaUrl ?? null,
217
+ isDefault: !!variant.isDefault,
218
+ isActive: !!variant.isActive
219
+ })),
220
+ prices: prices.map((price) => ({
221
+ id: price.id,
222
+ priceKindId: price.priceKind && typeof price.priceKind === "object" ? price.priceKind.id : price.priceKind ?? null,
223
+ currencyCode: price.currencyCode,
224
+ kind: price.kind,
225
+ minQuantity: price.minQuantity,
226
+ maxQuantity: price.maxQuantity ?? null,
227
+ unitPriceNet: price.unitPriceNet ?? null,
228
+ unitPriceGross: price.unitPriceGross ?? null,
229
+ channelId: price.channelId ?? null,
230
+ offerId: price.offer && typeof price.offer === "object" ? price.offer.id : price.offer ?? null,
231
+ variantId: price.variant && typeof price.variant === "object" ? price.variant.id : price.variant ?? null,
232
+ startsAt: price.startsAt ? new Date(price.startsAt).toISOString() : null,
233
+ endsAt: price.endsAt ? new Date(price.endsAt).toISOString() : null
234
+ })),
235
+ media: mediaAttachments.map((attachment) => ({
236
+ id: attachment.id,
237
+ fileName: attachment.fileName,
238
+ mimeType: attachment.mimeType,
239
+ fileSize: attachment.fileSize,
240
+ url: attachment.url
241
+ })),
242
+ unitConversions: unitConversions.map((row) => ({
243
+ id: row.id,
244
+ unitCode: row.unitCode,
245
+ toBaseFactor: row.toBaseFactor,
246
+ sortOrder: row.sortOrder,
247
+ isActive: !!row.isActive
248
+ })),
249
+ customFields
250
+ };
251
+ }
252
+ return {
253
+ found: true,
254
+ product: {
255
+ id: product.id,
256
+ title: product.title,
257
+ subtitle: product.subtitle ?? null,
258
+ description: product.description ?? null,
259
+ sku: product.sku ?? null,
260
+ handle: product.handle ?? null,
261
+ productType: product.productType,
262
+ statusEntryId: product.statusEntryId ?? null,
263
+ primaryCurrencyCode: product.primaryCurrencyCode ?? null,
264
+ taxRate: product.taxRate ?? null,
265
+ taxRateId: product.taxRateId ?? null,
266
+ defaultUnit: product.defaultUnit ?? null,
267
+ defaultSalesUnit: product.defaultSalesUnit ?? null,
268
+ defaultSalesUnitQuantity: product.defaultSalesUnitQuantity ?? null,
269
+ unitPriceEnabled: !!product.unitPriceEnabled,
270
+ unitPriceReferenceUnit: product.unitPriceReferenceUnit ?? null,
271
+ unitPriceBaseQuantity: product.unitPriceBaseQuantity ?? null,
272
+ defaultMediaId: product.defaultMediaId ?? null,
273
+ defaultMediaUrl: product.defaultMediaUrl ?? null,
274
+ imageUrl: product.defaultMediaUrl ?? null,
275
+ weightValue: product.weightValue ?? null,
276
+ weightUnit: product.weightUnit ?? null,
277
+ dimensions: product.dimensions ?? null,
278
+ metadata: product.metadata ?? null,
279
+ customFieldsetCode: product.customFieldsetCode ?? null,
280
+ isConfigurable: !!product.isConfigurable,
281
+ isActive: !!product.isActive,
282
+ organizationId: product.organizationId ?? null,
283
+ tenantId: product.tenantId ?? null,
284
+ createdAt: product.createdAt ? new Date(product.createdAt).toISOString() : null,
285
+ updatedAt: product.updatedAt ? new Date(product.updatedAt).toISOString() : null
286
+ },
287
+ customFields,
288
+ related
289
+ };
290
+ }
291
+ };
292
+ const productsAiTools = [listProductsTool, getProductTool];
293
+ var products_pack_default = productsAiTools;
294
+ export {
295
+ products_pack_default as default,
296
+ productsAiTools
297
+ };
298
+ //# sourceMappingURL=products-pack.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/catalog/ai-tools/products-pack.ts"],
4
+ "sourcesContent": ["/**\n * `catalog.list_products` + `catalog.get_product` (Phase 1 WS-C, Step 3.10).\n *\n * Read-only tools scoped to `ctx.tenantId` + `ctx.organizationId`. Mutation\n * tools are deferred to Step 5.14 under the pending-action contract.\n *\n * Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:\n * `catalog.list_products` is now an API-backed wrapper over\n * `GET /api/catalog/products`. Tool name, schema, requiredFeatures, and\n * output shape are unchanged.\n */\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'\nimport type {\n AiApiOperationRequest,\n AiToolExecutionContext,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport { Attachment } from '@open-mercato/core/modules/attachments/data/entities'\nimport {\n CatalogProduct,\n CatalogProductCategoryAssignment,\n CatalogProductTagAssignment,\n CatalogProductTag,\n CatalogProductVariant,\n CatalogProductPrice,\n CatalogProductUnitConversion,\n} from '../data/entities'\nimport { assertTenantScope, type CatalogAiToolDefinition, type CatalogToolContext } from './types'\n\nfunction resolveEm(ctx: CatalogToolContext | AiToolExecutionContext): EntityManager {\n return ctx.container.resolve<EntityManager>('em')\n}\n\nfunction buildScope(ctx: CatalogToolContext | AiToolExecutionContext, tenantId: string) {\n return { tenantId, organizationId: ctx.organizationId }\n}\n\nconst listProductsInput = z\n .object({\n q: z.string().trim().min(1).optional().describe('Optional search text matched against title / subtitle / sku / handle.'),\n limit: z.number().int().min(1).max(100).optional().describe('Maximum rows to return (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Number of rows to skip (default 0).'),\n categoryId: z.string().uuid().optional().describe('Restrict to products assigned to this catalog category.'),\n tagIds: z.array(z.string().uuid()).optional().describe('Restrict to products carrying at least one of these tag ids.'),\n active: z.boolean().optional().describe('When true, only active (not archived) products are returned.'),\n })\n .passthrough()\n\ntype ListProductsInput = z.infer<typeof listProductsInput>\n\ntype ListProductsApiItem = {\n id?: string\n title?: string | null\n subtitle?: string | null\n sku?: string | null\n handle?: string | null\n product_type?: string | null\n productType?: string | null\n status_entry_id?: string | null\n statusEntryId?: string | null\n primary_currency_code?: string | null\n primaryCurrencyCode?: string | null\n default_media_id?: string | null\n defaultMediaId?: string | null\n default_media_url?: string | null\n defaultMediaUrl?: string | null\n is_active?: boolean | null\n isActive?: boolean | null\n is_configurable?: boolean | null\n isConfigurable?: boolean | null\n organization_id?: string | null\n organizationId?: string | null\n tenant_id?: string | null\n tenantId?: string | null\n created_at?: string | null\n createdAt?: string | null\n updated_at?: string | null\n updatedAt?: string | null\n}\n\ntype ListProductsApiResponse = {\n items?: ListProductsApiItem[]\n total?: number\n}\n\ntype ListProductsOutput = {\n items: Array<Record<string, unknown>>\n total: number\n limit: number\n offset: number\n}\n\nconst listProductsTool = defineApiBackedAiTool<\n ListProductsInput,\n ListProductsApiResponse,\n ListProductsOutput\n>({\n name: 'catalog.list_products',\n displayName: 'List products',\n description:\n 'Search / list catalog products for the caller tenant + organization. Returns { items, total, limit, offset }.',\n inputSchema: listProductsInput,\n requiredFeatures: ['catalog.products.view'],\n toOperation: (input, ctx) => {\n assertTenantScope(ctx as unknown as CatalogToolContext)\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const query: Record<string, string | number | boolean | null | undefined> = {\n page,\n pageSize: limit,\n }\n if (input.q?.trim()) query.search = input.q.trim()\n if (input.categoryId) query.categoryIds = input.categoryId\n if (input.tagIds && input.tagIds.length > 0) query.tagIds = input.tagIds.join(',')\n if (input.active === true) query.isActive = 'true'\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: '/catalog/products',\n query,\n }\n return operation\n },\n mapResponse: (response, input) => {\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const data = (response.data ?? {}) as ListProductsApiResponse\n const rawItems: ListProductsApiItem[] = Array.isArray(data.items) ? data.items : []\n return {\n items: rawItems.map((row) => {\n const createdAtRaw = row.created_at ?? row.createdAt ?? null\n const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null\n const updatedAtRaw = row.updated_at ?? row.updatedAt ?? null\n const updatedAt = updatedAtRaw ? new Date(String(updatedAtRaw)).toISOString() : null\n return {\n id: row.id,\n title: row.title ?? null,\n subtitle: row.subtitle ?? null,\n sku: row.sku ?? null,\n handle: row.handle ?? null,\n productType: row.product_type ?? row.productType ?? null,\n statusEntryId: row.status_entry_id ?? row.statusEntryId ?? null,\n primaryCurrencyCode: row.primary_currency_code ?? row.primaryCurrencyCode ?? null,\n defaultMediaId: row.default_media_id ?? row.defaultMediaId ?? null,\n defaultMediaUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,\n imageUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,\n isActive: !!(row.is_active ?? row.isActive),\n isConfigurable: !!(row.is_configurable ?? row.isConfigurable),\n organizationId: row.organization_id ?? row.organizationId ?? null,\n tenantId: row.tenant_id ?? row.tenantId ?? null,\n createdAt,\n updatedAt,\n }\n }),\n total: typeof data.total === 'number' ? data.total : 0,\n limit,\n offset,\n }\n },\n}) as unknown as CatalogAiToolDefinition\n\nconst getProductInput = z.object({\n productId: z.string().uuid().describe('Catalog product id (UUID).'),\n includeRelated: z\n .boolean()\n .optional()\n .describe(\n 'When true, include categories, tags, variants, prices (base + offers), media (metadata only), unit conversions, and custom fields (each related list capped at 100).',\n ),\n})\n\nconst getProductTool: CatalogAiToolDefinition = {\n name: 'catalog.get_product',\n displayName: 'Get product',\n description:\n 'Fetch a catalog product by id with core fields and (optionally) categories, tags, variants, prices, media metadata, unit conversions, and custom fields. Returns { found: false } when the record is outside tenant/org scope or missing.',\n inputSchema: getProductInput,\n requiredFeatures: ['catalog.products.view'],\n tags: ['read', 'catalog'],\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input = getProductInput.parse(rawInput)\n const em = resolveEm(ctx)\n const where: Record<string, unknown> = {\n id: input.productId,\n tenantId,\n deletedAt: null,\n }\n if (ctx.organizationId) where.organizationId = ctx.organizationId\n const product = await findOneWithDecryption<CatalogProduct>(\n em,\n CatalogProduct,\n where as any,\n undefined,\n buildScope(ctx, tenantId),\n )\n if (!product || product.tenantId !== tenantId) {\n return { found: false as const, productId: input.productId }\n }\n\n let related: Record<string, unknown> | null = null\n let customFields: Record<string, unknown> = {}\n if (input.includeRelated) {\n const scope = buildScope(ctx, tenantId)\n const [\n categoryAssignments,\n tagAssignments,\n variants,\n prices,\n mediaAttachments,\n unitConversions,\n customFieldValues,\n ] = await Promise.all([\n findWithDecryption<CatalogProductCategoryAssignment>(\n em,\n CatalogProductCategoryAssignment,\n { tenantId, product: product.id } as any,\n { limit: 100, populate: ['category'] as any } as any,\n scope,\n ),\n findWithDecryption<CatalogProductTagAssignment>(\n em,\n CatalogProductTagAssignment,\n { tenantId, product: product.id } as any,\n { limit: 100, populate: ['tag'] as any } as any,\n scope,\n ),\n findWithDecryption<CatalogProductVariant>(\n em,\n CatalogProductVariant,\n { tenantId, product: product.id, deletedAt: null } as any,\n { limit: 100, orderBy: { createdAt: 'asc' } as any } as any,\n scope,\n ),\n findWithDecryption<CatalogProductPrice>(\n em,\n CatalogProductPrice,\n { tenantId, product: product.id } as any,\n { limit: 100, orderBy: { createdAt: 'asc' } as any } as any,\n scope,\n ),\n findWithDecryption<Attachment>(\n em,\n Attachment,\n { tenantId, entityId: E.catalog.catalog_product, recordId: product.id } as any,\n { limit: 100, orderBy: { createdAt: 'asc' } as any } as any,\n scope,\n ),\n findWithDecryption<CatalogProductUnitConversion>(\n em,\n CatalogProductUnitConversion,\n { tenantId, product: product.id, deletedAt: null } as any,\n { limit: 100, orderBy: { sortOrder: 'asc', createdAt: 'asc' } as any } as any,\n scope,\n ),\n loadCustomFieldValues({\n em,\n entityId: E.catalog.catalog_product,\n recordIds: [product.id],\n tenantIdByRecord: { [product.id]: product.tenantId ?? null },\n organizationIdByRecord: { [product.id]: product.organizationId ?? null },\n tenantFallbacks: [product.tenantId ?? tenantId].filter((value): value is string => !!value),\n }),\n ])\n customFields = customFieldValues[product.id] ?? {}\n related = {\n categories: categoryAssignments\n .map((assignment) => {\n const category = (assignment as any).category\n if (!category || typeof category === 'string') {\n const fallbackId = typeof category === 'string' ? category : null\n return fallbackId ? { id: fallbackId, name: null, slug: null } : null\n }\n return {\n id: category.id,\n name: category.name ?? null,\n slug: category.slug ?? null,\n }\n })\n .filter((value): value is { id: string; name: string | null; slug: string | null } => value !== null),\n tags: tagAssignments\n .map((assignment) => {\n const tag = (assignment as any).tag as CatalogProductTag | string | null\n if (!tag || typeof tag === 'string') return null\n return { id: tag.id, label: tag.label, slug: tag.slug }\n })\n .filter((value): value is { id: string; label: string; slug: string } => value !== null),\n variants: variants.map((variant) => ({\n id: variant.id,\n name: variant.name ?? null,\n sku: variant.sku ?? null,\n barcode: variant.barcode ?? null,\n optionValues: variant.optionValues ?? null,\n defaultMediaId: variant.defaultMediaId ?? null,\n defaultMediaUrl: variant.defaultMediaUrl ?? null,\n isDefault: !!variant.isDefault,\n isActive: !!variant.isActive,\n })),\n prices: prices.map((price) => ({\n id: price.id,\n priceKindId: (price as any).priceKind && typeof (price as any).priceKind === 'object'\n ? (price as any).priceKind.id\n : (price as any).priceKind ?? null,\n currencyCode: price.currencyCode,\n kind: price.kind,\n minQuantity: price.minQuantity,\n maxQuantity: price.maxQuantity ?? null,\n unitPriceNet: price.unitPriceNet ?? null,\n unitPriceGross: price.unitPriceGross ?? null,\n channelId: price.channelId ?? null,\n offerId: (price as any).offer && typeof (price as any).offer === 'object'\n ? (price as any).offer.id\n : (price as any).offer ?? null,\n variantId: (price as any).variant && typeof (price as any).variant === 'object'\n ? (price as any).variant.id\n : (price as any).variant ?? null,\n startsAt: price.startsAt ? new Date(price.startsAt).toISOString() : null,\n endsAt: price.endsAt ? new Date(price.endsAt).toISOString() : null,\n })),\n media: mediaAttachments.map((attachment) => ({\n id: attachment.id,\n fileName: attachment.fileName,\n mimeType: attachment.mimeType,\n fileSize: attachment.fileSize,\n url: attachment.url,\n })),\n unitConversions: unitConversions.map((row) => ({\n id: row.id,\n unitCode: row.unitCode,\n toBaseFactor: row.toBaseFactor,\n sortOrder: row.sortOrder,\n isActive: !!row.isActive,\n })),\n customFields,\n }\n }\n\n return {\n found: true as const,\n product: {\n id: product.id,\n title: product.title,\n subtitle: product.subtitle ?? null,\n description: product.description ?? null,\n sku: product.sku ?? null,\n handle: product.handle ?? null,\n productType: product.productType,\n statusEntryId: product.statusEntryId ?? null,\n primaryCurrencyCode: product.primaryCurrencyCode ?? null,\n taxRate: product.taxRate ?? null,\n taxRateId: product.taxRateId ?? null,\n defaultUnit: product.defaultUnit ?? null,\n defaultSalesUnit: product.defaultSalesUnit ?? null,\n defaultSalesUnitQuantity: product.defaultSalesUnitQuantity ?? null,\n unitPriceEnabled: !!product.unitPriceEnabled,\n unitPriceReferenceUnit: product.unitPriceReferenceUnit ?? null,\n unitPriceBaseQuantity: product.unitPriceBaseQuantity ?? null,\n defaultMediaId: product.defaultMediaId ?? null,\n defaultMediaUrl: product.defaultMediaUrl ?? null,\n imageUrl: product.defaultMediaUrl ?? null,\n weightValue: product.weightValue ?? null,\n weightUnit: product.weightUnit ?? null,\n dimensions: product.dimensions ?? null,\n metadata: product.metadata ?? null,\n customFieldsetCode: product.customFieldsetCode ?? null,\n isConfigurable: !!product.isConfigurable,\n isActive: !!product.isActive,\n organizationId: product.organizationId ?? null,\n tenantId: product.tenantId ?? null,\n createdAt: product.createdAt ? new Date(product.createdAt).toISOString() : null,\n updatedAt: product.updatedAt ? new Date(product.updatedAt).toISOString() : null,\n },\n customFields,\n related,\n }\n },\n}\n\nexport const productsAiTools: CatalogAiToolDefinition[] = [listProductsTool, getProductTool]\n\nexport default productsAiTools\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }