@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +13 -1
- package/dist/helpers/integration/api.js +29 -16
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/auth.js +11 -6
- package/dist/helpers/integration/auth.js.map +3 -3
- package/dist/modules/auth/commands/roles.js +9 -12
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/catalog/ai-agents-context.js +147 -0
- package/dist/modules/catalog/ai-agents-context.js.map +7 -0
- package/dist/modules/catalog/ai-agents.js +383 -0
- package/dist/modules/catalog/ai-agents.js.map +7 -0
- package/dist/modules/catalog/ai-tools/_shared.js +318 -0
- package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
- package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/types.js +10 -0
- package/dist/modules/catalog/ai-tools/types.js.map +7 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools.js +28 -0
- package/dist/modules/catalog/ai-tools.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
- package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
- package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
- package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/events.js +7 -4
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +13 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/ai-agents-context.js +96 -0
- package/dist/modules/customers/ai-agents-context.js.map +7 -0
- package/dist/modules/customers/ai-agents.js +244 -0
- package/dist/modules/customers/ai-agents.js.map +7 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
- package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
- package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/people-pack.js +261 -0
- package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
- package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/types.js +10 -0
- package/dist/modules/customers/ai-tools/types.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +20 -0
- package/dist/modules/customers/ai-tools.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection-table.js +26 -0
- package/dist/modules/customers/widgets/injection-table.js.map +7 -0
- package/dist/modules/inbox_ops/ai-tools.js +4 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/notifications/setup.js +13 -0
- package/dist/modules/notifications/setup.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.setup.ts +18 -0
- package/package.json +5 -3
- package/src/helpers/integration/api.ts +38 -16
- package/src/helpers/integration/auth.ts +13 -6
- package/src/modules/auth/commands/roles.ts +10 -12
- package/src/modules/catalog/AGENTS.md +11 -0
- package/src/modules/catalog/ai-agents-context.ts +239 -0
- package/src/modules/catalog/ai-agents.ts +525 -0
- package/src/modules/catalog/ai-tools/_shared.ts +487 -0
- package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
- package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
- package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
- package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
- package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
- package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
- package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
- package/src/modules/catalog/ai-tools/types.ts +81 -0
- package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
- package/src/modules/catalog/ai-tools.ts +78 -0
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
- package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
- package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
- package/src/modules/catalog/events.ts +7 -4
- package/src/modules/catalog/i18n/de.json +17 -0
- package/src/modules/catalog/i18n/en.json +17 -0
- package/src/modules/catalog/i18n/es.json +17 -0
- package/src/modules/catalog/i18n/pl.json +17 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/catalog/widgets/injection-table.ts +12 -0
- package/src/modules/customer_accounts/i18n/de.json +5 -0
- package/src/modules/customer_accounts/i18n/en.json +5 -0
- package/src/modules/customer_accounts/i18n/es.json +5 -0
- package/src/modules/customer_accounts/i18n/pl.json +5 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
- package/src/modules/customers/AGENTS.md +13 -0
- package/src/modules/customers/ai-agents-context.ts +150 -0
- package/src/modules/customers/ai-agents.ts +355 -0
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
- package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
- package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
- package/src/modules/customers/ai-tools/people-pack.ts +369 -0
- package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
- package/src/modules/customers/ai-tools/types.ts +76 -0
- package/src/modules/customers/ai-tools.ts +34 -0
- package/src/modules/customers/i18n/de.json +25 -0
- package/src/modules/customers/i18n/en.json +25 -0
- package/src/modules/customers/i18n/es.json +25 -0
- package/src/modules/customers/i18n/pl.json +25 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
- package/src/modules/customers/widgets/injection-table.ts +41 -0
- package/src/modules/inbox_ops/ai-tools.ts +4 -0
- package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
- 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": "AAuBA,SAAS,SAAS;AAClB,SAAS,6BAA6B;AAKtC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,yBAAgF;AACzF,SAAS,0BAA0B;AAEnC,SAAS,UAAU,KAAiE;AAClF,SAAO,IAAI,UAAU,QAAuB,IAAI;AAClD;AAEA,SAAS,WAAW,KAAkD,UAAkB;AACtF,SAAO,EAAE,UAAU,gBAAgB,IAAI,eAAe;AACxD;AAEA,MAAM,kBAAkB,EACrB,OAAO;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,8CAA8C;AAAA,EAC/F,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,8CAA8C;AAAA,EAC/F,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,8BAA8B;AAAA,EACjF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,iCAAiC;AAAA,EAC7F,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,2BAA2B;AACjF,CAAC,EACA,YAAY;AA+Df,MAAM,iBAAiB,sBAIrB;AAAA,EACA,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,uBAAuB;AAAA,EAC1C,aAAa,CAAC,OAAO,QAAQ;AAC3B,sBAAkB,GAAoC;AACtD,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAO,KAAK,MAAM,SAAS,KAAK,IAAI;AAE1C,UAAM,QAAsE;AAAA,MAC1E;AAAA,MACA,UAAU;AAAA,IACZ;AACA,QAAI,MAAM,UAAW,OAAM,YAAY,MAAM;AAC7C,QAAI,MAAM,UAAW,OAAM,YAAY,MAAM;AAC7C,QAAI,MAAM,YAAa,OAAM,cAAc,MAAM;AAEjD,UAAM,YAAmC;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,aAAa,CAAC,UAAU,UAAU;AAChC,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAQ,SAAS,QAAQ,CAAC;AAChC,UAAM,WAAgC,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAChF,WAAO;AAAA,MACL,OAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,cAAM,cAAc,IAAI,aAAa,IAAI,YAAY;AACrD,cAAM,WAAW,cAAc,IAAI,KAAK,OAAO,WAAW,CAAC,EAAE,YAAY,IAAI;AAC7E,cAAM,YAAY,IAAI,WAAW,IAAI,UAAU;AAC/C,cAAM,SAAS,YAAY,IAAI,KAAK,OAAO,SAAS,CAAC,EAAE,YAAY,IAAI;AACvE,cAAM,eAAe,IAAI,cAAc,IAAI,aAAa;AACxD,cAAM,YAAY,eAAe,IAAI,KAAK,OAAO,YAAY,CAAC,EAAE,YAAY,IAAI;AAChF,eAAO;AAAA,UACL,IAAI,IAAI;AAAA,UACR,aAAa,IAAI,iBAAiB,IAAI,eAAe;AAAA,UACrD,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,SAAS,IAAI,YAAY,IAAI,WAAW;AAAA,UACxC,cAAc,IAAI,iBAAiB,IAAI,gBAAgB;AAAA,UACvD,MAAM,IAAI,QAAQ;AAAA,UAClB,aAAa,IAAI,gBAAgB,IAAI,eAAe;AAAA,UACpD,aAAa,IAAI,gBAAgB,IAAI,eAAe;AAAA,UACpD,cAAc,IAAI,kBAAkB,IAAI,gBAAgB;AAAA,UACxD,gBAAgB,IAAI,oBAAoB,IAAI,kBAAkB;AAAA,UAC9D,SAAS,IAAI,YAAY,IAAI,WAAW;AAAA,UACxC,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,QAAQ,IAAI,WAAW,IAAI,UAAU;AAAA,UACrC,aAAa,IAAI,iBAAiB,IAAI,eAAe;AAAA,UACrD,YAAY,IAAI,eAAe,IAAI,cAAc;AAAA,UACjD,iBAAiB,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,UACjE;AAAA,UACA;AAAA,UACA,gBAAgB,IAAI,mBAAmB,IAAI,kBAAkB;AAAA,UAC7D,UAAU,IAAI,aAAa,IAAI,YAAY;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,CAAC;AAAA,MACD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MACrD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,MAAM,sBAAsB,EACzB,OAAO;AAAA,EACN,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,iCAAiC;AAAA,EAC7F,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,2BAA2B;AACjF,CAAC,EACA,YAAY;AAEf,MAAM,qBAA8C;AAAA,EAClD,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,yBAAyB;AAAA,EAC5C,MAAM,CAAC,QAAQ,SAAS;AAAA,EACxB,SAAS,OAAO,UAAU,QAAQ;AAChC,UAAM,EAAE,SAAS,IAAI,kBAAkB,GAAG;AAC1C,UAAM,QAAQ,oBAAoB,MAAM,QAAQ;AAGhD,WAAO,mBAAmB,KAAK,OAAO,QAAQ;AAAA,EAChD;AACF;AAEA,MAAM,kBAAkB,EACrB,OAAO;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,sCAAsC;AAAA,EACvF,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,qDAAqD;AAAA,EACtG,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,4DAA4D;AAAA,EACpG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,iCAAiC;AAAA,EAC7F,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,2BAA2B;AACjF,CAAC,EACA,YAAY;AAEf,MAAM,WAAW;AAsCjB,eAAe,0BACb,KACA,UACA,WACmB;AACnB,QAAM,KAAK,UAAU,GAAG;AACxB,QAAM,aAAsC,EAAE,UAAU,SAAS,UAAU;AAC3E,MAAI,IAAI,eAAgB,YAAW,iBAAiB,IAAI;AACxD,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,KAAK,QAAQ;AAAA,EAC1B;AACA,QAAM,WAAW,OACd,IAAI,CAAC,UAAW,MAAc,KAAK,EACnC,IAAI,CAAC,UAAW,SAAS,OAAO,UAAU,WAAW,MAAM,KAAK,KAAM,EACtE,OAAO,CAAC,UAA0C,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAClG,SAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;AACrC;AAEA,MAAM,iBAAiB,sBAIrB;AAAA,EACA,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,uBAAuB;AAAA,EAC1C,aAAa,OAAO,OAAO,QAAQ;AACjC,UAAM,EAAE,SAAS,IAAI,kBAAkB,GAAoC;AAC3E,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAO,KAAK,MAAM,SAAS,KAAK,IAAI;AAE1C,UAAM,QAAsE;AAAA,MAC1E;AAAA,MACA,UAAU;AAAA,IACZ;AACA,QAAI,MAAM,UAAW,OAAM,YAAY,MAAM;AAC7C,QAAI,MAAM,WAAW,KAAM,OAAM,WAAW;AAE5C,QAAI,MAAM,WAAW;AACnB,YAAM,WAAW,MAAM,0BAA0B,KAAK,UAAU,MAAM,SAAS;AAC/E,UAAI,SAAS,WAAW,GAAG;AAGzB,cAAM,KAAK;AAAA,MACb,WAAW,SAAS,WAAW,GAAG;AAChC,cAAM,KAAK,SAAS,CAAC;AAAA,MACvB;AAAA,IAGF;AAEA,UAAM,YAAmC;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,aAAa,OAAO,UAAU,OAAO,QAAQ;AAC3C,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAQ,SAAS,QAAQ,CAAC;AAChC,QAAI,WAAgC,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAC9E,QAAI,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAE1D,QAAI,MAAM,WAAW;AACnB,YAAM,EAAE,SAAS,IAAI,kBAAkB,GAAoC;AAC3E,YAAM,WAAW,MAAM,0BAA0B,KAAK,UAAU,MAAM,SAAS;AAC/E,YAAM,aAAa,IAAI,IAAI,QAAQ;AACnC,iBAAW,SAAS,OAAO,CAAC,QAAQ,OAAO,IAAI,OAAO,YAAY,WAAW,IAAI,IAAI,EAAE,CAAC;AACxF,cAAQ,SAAS;AAAA,IACnB;AAEA,WAAO;AAAA,MACL,OAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,cAAM,eAAe,IAAI,cAAc,IAAI,aAAa;AACxD,cAAM,YAAY,eAAe,IAAI,KAAK,OAAO,YAAY,CAAC,EAAE,YAAY,IAAI;AAChF,eAAO;AAAA,UACL,IAAI,IAAI;AAAA,UACR,OAAO,IAAI,SAAS;AAAA,UACpB,aAAa,IAAI,eAAe;AAAA,UAChC,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,WAAW,IAAI,cAAc,IAAI,aAAa;AAAA,UAC9C,gBAAgB,IAAI,oBAAoB,IAAI,kBAAkB;AAAA,UAC9D,iBAAiB,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,UACjE,UAAU,CAAC,EAAE,IAAI,aAAa,IAAI;AAAA,UAClC,gBAAgB,IAAI,mBAAmB,IAAI,kBAAkB;AAAA,UAC7D,UAAU,IAAI,aAAa,IAAI,YAAY;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,CAAC;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAEM,MAAM,sBAAiD;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAO,6BAAQ;",
|
|
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": "AAYA,SAAS,SAAS;AAClB,SAAS,6BAA6B;AAKtC,SAAS,uBAAuB,0BAA0B;AAC1D,SAAS,6BAA6B;AACtC,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAgF;AAEzF,SAAS,UAAU,KAAiE;AAClF,SAAO,IAAI,UAAU,QAAuB,IAAI;AAClD;AAEA,SAAS,WAAW,KAAkD,UAAkB;AACtF,SAAO,EAAE,UAAU,gBAAgB,IAAI,eAAe;AACxD;AAEA,MAAM,oBAAoB,EACvB,OAAO;AAAA,EACN,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,uEAAuE;AAAA,EACvH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,+CAA+C;AAAA,EAC3G,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,EACzF,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,yDAAyD;AAAA,EAC3G,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,SAAS,8DAA8D;AAAA,EACrH,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,8DAA8D;AACxG,CAAC,EACA,YAAY;AA8Cf,MAAM,mBAAmB,sBAIvB;AAAA,EACA,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,uBAAuB;AAAA,EAC1C,aAAa,CAAC,OAAO,QAAQ;AAC3B,sBAAkB,GAAoC;AACtD,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAO,KAAK,MAAM,SAAS,KAAK,IAAI;AAE1C,UAAM,QAAsE;AAAA,MAC1E;AAAA,MACA,UAAU;AAAA,IACZ;AACA,QAAI,MAAM,GAAG,KAAK,EAAG,OAAM,SAAS,MAAM,EAAE,KAAK;AACjD,QAAI,MAAM,WAAY,OAAM,cAAc,MAAM;AAChD,QAAI,MAAM,UAAU,MAAM,OAAO,SAAS,EAAG,OAAM,SAAS,MAAM,OAAO,KAAK,GAAG;AACjF,QAAI,MAAM,WAAW,KAAM,OAAM,WAAW;AAE5C,UAAM,YAAmC;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,aAAa,CAAC,UAAU,UAAU;AAChC,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAQ,SAAS,QAAQ,CAAC;AAChC,UAAM,WAAkC,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAClF,WAAO;AAAA,MACL,OAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,cAAM,eAAe,IAAI,cAAc,IAAI,aAAa;AACxD,cAAM,YAAY,eAAe,IAAI,KAAK,OAAO,YAAY,CAAC,EAAE,YAAY,IAAI;AAChF,cAAM,eAAe,IAAI,cAAc,IAAI,aAAa;AACxD,cAAM,YAAY,eAAe,IAAI,KAAK,OAAO,YAAY,CAAC,EAAE,YAAY,IAAI;AAChF,eAAO;AAAA,UACL,IAAI,IAAI;AAAA,UACR,OAAO,IAAI,SAAS;AAAA,UACpB,UAAU,IAAI,YAAY;AAAA,UAC1B,KAAK,IAAI,OAAO;AAAA,UAChB,QAAQ,IAAI,UAAU;AAAA,UACtB,aAAa,IAAI,gBAAgB,IAAI,eAAe;AAAA,UACpD,eAAe,IAAI,mBAAmB,IAAI,iBAAiB;AAAA,UAC3D,qBAAqB,IAAI,yBAAyB,IAAI,uBAAuB;AAAA,UAC7E,gBAAgB,IAAI,oBAAoB,IAAI,kBAAkB;AAAA,UAC9D,iBAAiB,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,UACjE,UAAU,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,UAC1D,UAAU,CAAC,EAAE,IAAI,aAAa,IAAI;AAAA,UAClC,gBAAgB,CAAC,EAAE,IAAI,mBAAmB,IAAI;AAAA,UAC9C,gBAAgB,IAAI,mBAAmB,IAAI,kBAAkB;AAAA,UAC7D,UAAU,IAAI,aAAa,IAAI,YAAY;AAAA,UAC3C;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,MACD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MACrD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,MAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,4BAA4B;AAAA,EAClE,gBAAgB,EACb,QAAQ,EACR,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAED,MAAM,iBAA0C;AAAA,EAC9C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,uBAAuB;AAAA,EAC1C,MAAM,CAAC,QAAQ,SAAS;AAAA,EACxB,SAAS,OAAO,UAAU,QAAQ;AAChC,UAAM,EAAE,SAAS,IAAI,kBAAkB,GAAG;AAC1C,UAAM,QAAQ,gBAAgB,MAAM,QAAQ;AAC5C,UAAM,KAAK,UAAU,GAAG;AACxB,UAAM,QAAiC;AAAA,MACrC,IAAI,MAAM;AAAA,MACV;AAAA,MACA,WAAW;AAAA,IACb;AACA,QAAI,IAAI,eAAgB,OAAM,iBAAiB,IAAI;AACnD,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,KAAK,QAAQ;AAAA,IAC1B;AACA,QAAI,CAAC,WAAW,QAAQ,aAAa,UAAU;AAC7C,aAAO,EAAE,OAAO,OAAgB,WAAW,MAAM,UAAU;AAAA,IAC7D;AAEA,QAAI,UAA0C;AAC9C,QAAI,eAAwC,CAAC;AAC7C,QAAI,MAAM,gBAAgB;AACxB,YAAM,QAAQ,WAAW,KAAK,QAAQ;AACtC,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,IAAI,MAAM,QAAQ,IAAI;AAAA,QACpB;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,SAAS,QAAQ,GAAG;AAAA,UAChC,EAAE,OAAO,KAAK,UAAU,CAAC,UAAU,EAAS;AAAA,UAC5C;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,SAAS,QAAQ,GAAG;AAAA,UAChC,EAAE,OAAO,KAAK,UAAU,CAAC,KAAK,EAAS;AAAA,UACvC;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,SAAS,QAAQ,IAAI,WAAW,KAAK;AAAA,UACjD,EAAE,OAAO,KAAK,SAAS,EAAE,WAAW,MAAM,EAAS;AAAA,UACnD;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,SAAS,QAAQ,GAAG;AAAA,UAChC,EAAE,OAAO,KAAK,SAAS,EAAE,WAAW,MAAM,EAAS;AAAA,UACnD;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,UAAU,EAAE,QAAQ,iBAAiB,UAAU,QAAQ,GAAG;AAAA,UACtE,EAAE,OAAO,KAAK,SAAS,EAAE,WAAW,MAAM,EAAS;AAAA,UACnD;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,UAAU,SAAS,QAAQ,IAAI,WAAW,KAAK;AAAA,UACjD,EAAE,OAAO,KAAK,SAAS,EAAE,WAAW,OAAO,WAAW,MAAM,EAAS;AAAA,UACrE;AAAA,QACF;AAAA,QACA,sBAAsB;AAAA,UACpB;AAAA,UACA,UAAU,EAAE,QAAQ;AAAA,UACpB,WAAW,CAAC,QAAQ,EAAE;AAAA,UACtB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,YAAY,KAAK;AAAA,UAC3D,wBAAwB,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,kBAAkB,KAAK;AAAA,UACvE,iBAAiB,CAAC,QAAQ,YAAY,QAAQ,EAAE,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,QAC5F,CAAC;AAAA,MACH,CAAC;AACD,qBAAe,kBAAkB,QAAQ,EAAE,KAAK,CAAC;AACjD,gBAAU;AAAA,QACR,YAAY,oBACT,IAAI,CAAC,eAAe;AACnB,gBAAM,WAAY,WAAmB;AACrC,cAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,kBAAM,aAAa,OAAO,aAAa,WAAW,WAAW;AAC7D,mBAAO,aAAa,EAAE,IAAI,YAAY,MAAM,MAAM,MAAM,KAAK,IAAI;AAAA,UACnE;AACA,iBAAO;AAAA,YACL,IAAI,SAAS;AAAA,YACb,MAAM,SAAS,QAAQ;AAAA,YACvB,MAAM,SAAS,QAAQ;AAAA,UACzB;AAAA,QACF,CAAC,EACA,OAAO,CAAC,UAA6E,UAAU,IAAI;AAAA,QACtG,MAAM,eACH,IAAI,CAAC,eAAe;AACnB,gBAAM,MAAO,WAAmB;AAChC,cAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,iBAAO,EAAE,IAAI,IAAI,IAAI,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK;AAAA,QACxD,CAAC,EACA,OAAO,CAAC,UAAgE,UAAU,IAAI;AAAA,QACzF,UAAU,SAAS,IAAI,CAAC,aAAa;AAAA,UACnC,IAAI,QAAQ;AAAA,UACZ,MAAM,QAAQ,QAAQ;AAAA,UACtB,KAAK,QAAQ,OAAO;AAAA,UACpB,SAAS,QAAQ,WAAW;AAAA,UAC5B,cAAc,QAAQ,gBAAgB;AAAA,UACtC,gBAAgB,QAAQ,kBAAkB;AAAA,UAC1C,iBAAiB,QAAQ,mBAAmB;AAAA,UAC5C,WAAW,CAAC,CAAC,QAAQ;AAAA,UACrB,UAAU,CAAC,CAAC,QAAQ;AAAA,QACtB,EAAE;AAAA,QACF,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,UAC7B,IAAI,MAAM;AAAA,UACV,aAAc,MAAc,aAAa,OAAQ,MAAc,cAAc,WACxE,MAAc,UAAU,KACxB,MAAc,aAAa;AAAA,UAChC,cAAc,MAAM;AAAA,UACpB,MAAM,MAAM;AAAA,UACZ,aAAa,MAAM;AAAA,UACnB,aAAa,MAAM,eAAe;AAAA,UAClC,cAAc,MAAM,gBAAgB;AAAA,UACpC,gBAAgB,MAAM,kBAAkB;AAAA,UACxC,WAAW,MAAM,aAAa;AAAA,UAC9B,SAAU,MAAc,SAAS,OAAQ,MAAc,UAAU,WAC5D,MAAc,MAAM,KACpB,MAAc,SAAS;AAAA,UAC5B,WAAY,MAAc,WAAW,OAAQ,MAAc,YAAY,WAClE,MAAc,QAAQ,KACtB,MAAc,WAAW;AAAA,UAC9B,UAAU,MAAM,WAAW,IAAI,KAAK,MAAM,QAAQ,EAAE,YAAY,IAAI;AAAA,UACpE,QAAQ,MAAM,SAAS,IAAI,KAAK,MAAM,MAAM,EAAE,YAAY,IAAI;AAAA,QAChE,EAAE;AAAA,QACF,OAAO,iBAAiB,IAAI,CAAC,gBAAgB;AAAA,UAC3C,IAAI,WAAW;AAAA,UACf,UAAU,WAAW;AAAA,UACrB,UAAU,WAAW;AAAA,UACrB,UAAU,WAAW;AAAA,UACrB,KAAK,WAAW;AAAA,QAClB,EAAE;AAAA,QACF,iBAAiB,gBAAgB,IAAI,CAAC,SAAS;AAAA,UAC7C,IAAI,IAAI;AAAA,UACR,UAAU,IAAI;AAAA,UACd,cAAc,IAAI;AAAA,UAClB,WAAW,IAAI;AAAA,UACf,UAAU,CAAC,CAAC,IAAI;AAAA,QAClB,EAAE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,QACP,IAAI,QAAQ;AAAA,QACZ,OAAO,QAAQ;AAAA,QACf,UAAU,QAAQ,YAAY;AAAA,QAC9B,aAAa,QAAQ,eAAe;AAAA,QACpC,KAAK,QAAQ,OAAO;AAAA,QACpB,QAAQ,QAAQ,UAAU;AAAA,QAC1B,aAAa,QAAQ;AAAA,QACrB,eAAe,QAAQ,iBAAiB;AAAA,QACxC,qBAAqB,QAAQ,uBAAuB;AAAA,QACpD,SAAS,QAAQ,WAAW;AAAA,QAC5B,WAAW,QAAQ,aAAa;AAAA,QAChC,aAAa,QAAQ,eAAe;AAAA,QACpC,kBAAkB,QAAQ,oBAAoB;AAAA,QAC9C,0BAA0B,QAAQ,4BAA4B;AAAA,QAC9D,kBAAkB,CAAC,CAAC,QAAQ;AAAA,QAC5B,wBAAwB,QAAQ,0BAA0B;AAAA,QAC1D,uBAAuB,QAAQ,yBAAyB;AAAA,QACxD,gBAAgB,QAAQ,kBAAkB;AAAA,QAC1C,iBAAiB,QAAQ,mBAAmB;AAAA,QAC5C,UAAU,QAAQ,mBAAmB;AAAA,QACrC,aAAa,QAAQ,eAAe;AAAA,QACpC,YAAY,QAAQ,cAAc;AAAA,QAClC,YAAY,QAAQ,cAAc;AAAA,QAClC,UAAU,QAAQ,YAAY;AAAA,QAC9B,oBAAoB,QAAQ,sBAAsB;AAAA,QAClD,gBAAgB,CAAC,CAAC,QAAQ;AAAA,QAC1B,UAAU,CAAC,CAAC,QAAQ;AAAA,QACpB,gBAAgB,QAAQ,kBAAkB;AAAA,QAC1C,UAAU,QAAQ,YAAY;AAAA,QAC9B,WAAW,QAAQ,YAAY,IAAI,KAAK,QAAQ,SAAS,EAAE,YAAY,IAAI;AAAA,QAC3E,WAAW,QAAQ,YAAY,IAAI,KAAK,QAAQ,SAAS,EAAE,YAAY,IAAI;AAAA,MAC7E;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,kBAA6C,CAAC,kBAAkB,cAAc;AAE3F,IAAO,wBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|