@yoryoboy/bi-mcp 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +6 -3
- package/dist/src/services/mercadolibre/mercadolibre-billing.js +77 -0
- package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +7 -0
- package/dist/src/services/mercadolibre/mercadolibre-items.js +29 -2
- package/dist/src/services/mercadolibre/mercadolibre-items.js.map +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-orders.js +18 -0
- package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
- package/dist/src/tools/mercadolibre/estimate-listing-fee.js +26 -34
- package/dist/src/tools/mercadolibre/estimate-listing-fee.js.map +2 -2
- package/dist/src/tools/mercadolibre/estimate-product-profitability.js +546 -0
- package/dist/src/tools/mercadolibre/estimate-product-profitability.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-item-details.js +3 -25
- package/dist/src/tools/mercadolibre/get-item-details.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-sales-by-item.js +129 -14
- package/dist/src/tools/mercadolibre/get-sales-by-item.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-sales-trend.js +137 -14
- package/dist/src/tools/mercadolibre/get-sales-trend.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-shipping-summary.js +450 -57
- package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-site-categories-and-listing-types.js +50 -0
- package/dist/src/tools/mercadolibre/get-site-categories-and-listing-types.js.map +7 -0
- package/dist/src/tools/mercadolibre/index.js +2 -0
- package/dist/src/tools/mercadolibre/index.js.map +2 -2
- package/dist/src/tools/mercadolibre/listing-fee-helpers.js +265 -0
- package/dist/src/tools/mercadolibre/listing-fee-helpers.js.map +7 -0
- package/dist/src/tools/mercadolibre/search-items.js +207 -52
- package/dist/src/tools/mercadolibre/search-items.js.map +2 -2
- package/package.json +1 -1
|
@@ -1,48 +1,167 @@
|
|
|
1
1
|
import { error, object } from "mcp-use/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getMercadoLibreAccessForProfile } from "../../config/mercadolibre.js";
|
|
4
|
-
import {
|
|
5
|
-
buildMercadoLibrePaginationMetadata,
|
|
6
|
-
formatMercadoLibreError,
|
|
7
|
-
normalizeMercadoLibrePaging
|
|
8
|
-
} from "../../services/mercadolibre/mercadolibre-api.js";
|
|
4
|
+
import { formatMercadoLibreError, normalizeMercadoLibrePaging } from "../../services/mercadolibre/mercadolibre-api.js";
|
|
9
5
|
import {
|
|
10
6
|
getMercadoLibreItemDetailsBatch,
|
|
11
|
-
|
|
7
|
+
getMercadoLibreItemDetailsWithFallbackBatch,
|
|
8
|
+
searchMercadoLibreItems,
|
|
9
|
+
searchMercadoLibreItemsBatch
|
|
12
10
|
} from "../../services/mercadolibre/mercadolibre-items.js";
|
|
13
11
|
import { stripNulls } from "../../utils/strip-payload.js";
|
|
14
|
-
import { asArray, normalizeString, toNumber } from "./helpers.js";
|
|
12
|
+
import { asArray, asRecord, normalizeString, toNumber } from "./helpers.js";
|
|
15
13
|
import { resolveMercadoLibreProfileOrSelection } from "./profile-resolution.js";
|
|
16
14
|
import { mercadolibreProfileIdSchemaField } from "./write-helpers.js";
|
|
15
|
+
const DEFAULT_STATUS = "active";
|
|
16
|
+
const SEARCH_PAGE_LIMIT = 50;
|
|
17
|
+
const DETAIL_BATCH_SIZE = 20;
|
|
18
|
+
const PAGE_FETCH_CONCURRENCY = 15;
|
|
19
|
+
const PAGE_FETCH_MAX_RETRIES = 2;
|
|
20
|
+
const AVAILABLE_STATUS_FILTERS = [
|
|
21
|
+
"active",
|
|
22
|
+
"paused",
|
|
23
|
+
"closed",
|
|
24
|
+
"under_review",
|
|
25
|
+
"inactive",
|
|
26
|
+
"not_yet_active",
|
|
27
|
+
"payment_required",
|
|
28
|
+
"deleted"
|
|
29
|
+
];
|
|
30
|
+
const itemsSchema = [
|
|
31
|
+
"item_id",
|
|
32
|
+
"title",
|
|
33
|
+
"status",
|
|
34
|
+
"price",
|
|
35
|
+
"currency_id",
|
|
36
|
+
"available_quantity",
|
|
37
|
+
"sold_quantity",
|
|
38
|
+
"listing_type_id",
|
|
39
|
+
"category_id"
|
|
40
|
+
];
|
|
17
41
|
const meliSearchItemsSchema = z.object({
|
|
18
42
|
profileId: mercadolibreProfileIdSchemaField,
|
|
19
|
-
status: z.string().trim().min(1).optional().describe("Optional item status filter."),
|
|
43
|
+
status: z.string().trim().min(1).optional().describe("Optional item status filter. Defaults to active when omitted."),
|
|
44
|
+
title: z.string().trim().min(1).optional().describe("Optional item title or text query. Maps to MercadoLibre q search."),
|
|
20
45
|
category: z.string().trim().min(1).optional().describe("Optional category id filter."),
|
|
21
46
|
listingTypeId: z.string().trim().min(1).optional().describe("Optional listing type filter."),
|
|
22
47
|
orders: z.string().trim().min(1).optional().describe("Optional MercadoLibre sort expression for items search."),
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
offset: z.number().int().min(0).max(5e3).optional().describe("Pagination offset.")
|
|
48
|
+
limit: z.number().int().min(1).max(50).optional().describe("Optional maximum detailed items to return. If omitted, returns the full matching universe from offset onward."),
|
|
49
|
+
offset: z.number().int().min(0).max(5e3).optional().describe("Pagination offset over the matching MercadoLibre items universe.")
|
|
26
50
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
function formatCompactItem(item) {
|
|
52
|
+
return [
|
|
53
|
+
normalizeString(item.id),
|
|
54
|
+
normalizeString(item.title),
|
|
55
|
+
normalizeString(item.status),
|
|
56
|
+
toNumber(item.price),
|
|
57
|
+
normalizeString(item.currency_id, "UNKNOWN"),
|
|
58
|
+
toNumber(item.available_quantity),
|
|
59
|
+
toNumber(item.sold_quantity),
|
|
60
|
+
normalizeString(item.listing_type_id),
|
|
61
|
+
normalizeString(item.category_id)
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
function buildItemsMetadata(params) {
|
|
65
|
+
const nextOffset = params.offset + params.returned;
|
|
66
|
+
const hasMore = nextOffset < params.total;
|
|
67
|
+
let continuation = "No more pages for this query.";
|
|
68
|
+
if (params.pagesFailed > 0 || params.detailFailures > 0) {
|
|
69
|
+
continuation = hasMore ? `Se recuper\xF3 cobertura parcial. Reintentar la misma tool con offset=${params.offset} y limit=${params.limitWasExplicit ? params.effectiveLimit : 50}. Luego continuar con offset=${nextOffset} si hace falta.` : "Se recuper\xF3 cobertura parcial. Reintentar la misma tool con los mismos filtros si hace falta completar la cobertura.";
|
|
70
|
+
} else if (params.limitWasExplicit && hasMore) {
|
|
71
|
+
continuation = `Para continuar trayendo items, volver a invocar meli_search_items con los mismos filtros, offset=${nextOffset} y limit=${params.effectiveLimit}. Repetir hasta has_more=false.`;
|
|
72
|
+
}
|
|
73
|
+
return stripNulls({
|
|
74
|
+
total: params.total,
|
|
75
|
+
limit: params.effectiveLimit,
|
|
76
|
+
offset: params.offset,
|
|
77
|
+
returned: params.returned,
|
|
78
|
+
has_more: params.limitWasExplicit ? hasMore || params.pagesFailed > 0 || params.detailFailures > 0 : params.pagesFailed > 0 || params.detailFailures > 0,
|
|
79
|
+
next_offset: params.limitWasExplicit && hasMore ? nextOffset : void 0,
|
|
80
|
+
continuation,
|
|
81
|
+
applied_status: params.appliedStatus,
|
|
82
|
+
default_status_notice: params.usedDefaultStatus ? "Solo se devolvieron publicaciones active por default. Tambi\xE9n se puede pedir status expl\xEDcito como paused, closed, under_review, inactive, not_yet_active, payment_required o deleted." : void 0,
|
|
83
|
+
available_status_filters: AVAILABLE_STATUS_FILTERS,
|
|
84
|
+
detail_coverage: {
|
|
85
|
+
detail_failures: params.detailFailures,
|
|
86
|
+
details_complete: params.detailFailures === 0
|
|
87
|
+
},
|
|
88
|
+
pages_requested: params.pagesRequested,
|
|
89
|
+
pages_succeeded: params.pagesSucceeded,
|
|
90
|
+
pages_failed: params.pagesFailed,
|
|
91
|
+
universe_fully_fetched: params.universeFullyFetched
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function fetchAllMatchingItemIds(profileId, sellerId, params) {
|
|
95
|
+
const appliedStatus = params.status ?? DEFAULT_STATUS;
|
|
96
|
+
const firstResponse = await searchMercadoLibreItems(profileId, {
|
|
97
|
+
sellerId,
|
|
98
|
+
status: appliedStatus,
|
|
99
|
+
q: params.title,
|
|
100
|
+
category: params.category,
|
|
101
|
+
listingTypeId: params.listingTypeId,
|
|
102
|
+
orders: params.orders,
|
|
103
|
+
limit: SEARCH_PAGE_LIMIT,
|
|
104
|
+
offset: 0
|
|
105
|
+
});
|
|
106
|
+
const firstPaging = normalizeMercadoLibrePaging(asRecord(firstResponse.paging));
|
|
107
|
+
const total = firstPaging.total;
|
|
108
|
+
const effectiveLimit = firstPaging.limit || SEARCH_PAGE_LIMIT;
|
|
109
|
+
const ids = asArray(firstResponse.results).map((value) => normalizeString(value)).filter(Boolean);
|
|
110
|
+
const failedOffsets = [];
|
|
111
|
+
let pagesRequested = 1;
|
|
112
|
+
let pagesSucceeded = 1;
|
|
113
|
+
if (effectiveLimit > 0 && total > ids.length) {
|
|
114
|
+
const nextOffsets = [];
|
|
115
|
+
for (let nextOffset = effectiveLimit; nextOffset < total; nextOffset += effectiveLimit) {
|
|
116
|
+
nextOffsets.push(nextOffset);
|
|
32
117
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
118
|
+
pagesRequested += nextOffsets.length;
|
|
119
|
+
if (nextOffsets.length > 0) {
|
|
120
|
+
const batch = await searchMercadoLibreItemsBatch(
|
|
121
|
+
profileId,
|
|
122
|
+
{
|
|
123
|
+
sellerId,
|
|
124
|
+
status: appliedStatus,
|
|
125
|
+
q: params.title,
|
|
126
|
+
category: params.category,
|
|
127
|
+
listingTypeId: params.listingTypeId,
|
|
128
|
+
orders: params.orders,
|
|
129
|
+
limit: effectiveLimit
|
|
130
|
+
},
|
|
131
|
+
nextOffsets,
|
|
132
|
+
{
|
|
133
|
+
maxConcurrency: PAGE_FETCH_CONCURRENCY,
|
|
134
|
+
maxRetries: PAGE_FETCH_MAX_RETRIES
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
pagesSucceeded += batch.successful.length;
|
|
138
|
+
batch.successful.forEach((entry) => {
|
|
139
|
+
ids.push(...asArray(entry.document.results).map((value) => normalizeString(value)).filter(Boolean));
|
|
140
|
+
});
|
|
141
|
+
batch.failed.forEach((failure) => {
|
|
142
|
+
failedOffsets.push(Number(failure.id));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
ids,
|
|
148
|
+
total,
|
|
149
|
+
appliedStatus,
|
|
150
|
+
pagesRequested,
|
|
151
|
+
pagesSucceeded,
|
|
152
|
+
pagesFailed: failedOffsets.length,
|
|
153
|
+
universeFullyFetched: failedOffsets.length === 0 && ids.length >= total
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function fetchDetailedItems(profileId, itemIds) {
|
|
157
|
+
const details = {};
|
|
158
|
+
let detailFailures = 0;
|
|
159
|
+
for (let index = 0; index < itemIds.length; index += DETAIL_BATCH_SIZE) {
|
|
160
|
+
const chunk = itemIds.slice(index, index + DETAIL_BATCH_SIZE);
|
|
161
|
+
if (chunk.length === 0) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const batch = await getMercadoLibreItemDetailsBatch(profileId, chunk, [
|
|
46
165
|
"id",
|
|
47
166
|
"title",
|
|
48
167
|
"status",
|
|
@@ -51,33 +170,69 @@ async function meliSearchItemsHandler(params) {
|
|
|
51
170
|
"available_quantity",
|
|
52
171
|
"sold_quantity",
|
|
53
172
|
"listing_type_id",
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
173
|
+
"category_id"
|
|
174
|
+
]);
|
|
175
|
+
const foundIds = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const item of batch) {
|
|
177
|
+
const itemId = normalizeString(item.id);
|
|
178
|
+
if (!itemId) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
foundIds.add(itemId);
|
|
182
|
+
details[itemId] = item;
|
|
183
|
+
}
|
|
184
|
+
const missingIds = chunk.filter((itemId) => !foundIds.has(itemId));
|
|
185
|
+
if (missingIds.length > 0) {
|
|
186
|
+
const fallback = await getMercadoLibreItemDetailsWithFallbackBatch(profileId, missingIds, {
|
|
187
|
+
maxConcurrency: 10,
|
|
188
|
+
maxRetries: 1
|
|
189
|
+
});
|
|
190
|
+
fallback.successful.forEach((entry) => {
|
|
191
|
+
details[entry.id] = entry.document;
|
|
192
|
+
});
|
|
193
|
+
detailFailures += fallback.failed.length;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
orderedItems: itemIds.map((itemId) => details[itemId]).filter(Boolean),
|
|
198
|
+
detailFailures
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function meliSearchItemsHandler(params) {
|
|
202
|
+
try {
|
|
203
|
+
const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);
|
|
204
|
+
if (!profileResolution.ok) {
|
|
205
|
+
return profileResolution.response;
|
|
206
|
+
}
|
|
207
|
+
const profileId = profileResolution.value.profileId;
|
|
208
|
+
const access = await getMercadoLibreAccessForProfile(profileId);
|
|
209
|
+
const requestedOffset = params.offset ?? 0;
|
|
210
|
+
const limitWasExplicit = typeof params.limit === "number";
|
|
211
|
+
const usedDefaultStatus = !params.status;
|
|
212
|
+
const { ids, total, appliedStatus, pagesRequested, pagesSucceeded, pagesFailed, universeFullyFetched } = await fetchAllMatchingItemIds(profileId, access.sellerId, params);
|
|
213
|
+
const pageIds = limitWasExplicit ? ids.slice(requestedOffset, requestedOffset + (params.limit ?? 0)) : ids.slice(requestedOffset);
|
|
214
|
+
const effectiveLimit = limitWasExplicit ? params.limit ?? pageIds.length : Math.max(pageIds.length, 0);
|
|
215
|
+
const { orderedItems, detailFailures } = await fetchDetailedItems(profileId, pageIds);
|
|
216
|
+
const compactItems = orderedItems.map((item) => formatCompactItem(item));
|
|
58
217
|
return object(
|
|
59
218
|
stripNulls({
|
|
60
219
|
profile_id: profileId,
|
|
61
|
-
metadata:
|
|
62
|
-
total
|
|
63
|
-
|
|
64
|
-
offset:
|
|
65
|
-
returned:
|
|
66
|
-
|
|
220
|
+
metadata: buildItemsMetadata({
|
|
221
|
+
total,
|
|
222
|
+
effectiveLimit,
|
|
223
|
+
offset: requestedOffset,
|
|
224
|
+
returned: compactItems.length,
|
|
225
|
+
appliedStatus,
|
|
226
|
+
usedDefaultStatus,
|
|
227
|
+
detailFailures,
|
|
228
|
+
pagesRequested,
|
|
229
|
+
pagesSucceeded,
|
|
230
|
+
pagesFailed,
|
|
231
|
+
universeFullyFetched,
|
|
232
|
+
limitWasExplicit
|
|
67
233
|
}),
|
|
68
|
-
|
|
69
|
-
items: compactItems
|
|
70
|
-
item_id: normalizeString(item.id),
|
|
71
|
-
title: normalizeString(item.title),
|
|
72
|
-
status: normalizeString(item.status),
|
|
73
|
-
price: toNumber(item.price),
|
|
74
|
-
currency_id: normalizeString(item.currency_id, "UNKNOWN"),
|
|
75
|
-
available_quantity: toNumber(item.available_quantity),
|
|
76
|
-
sold_quantity: toNumber(item.sold_quantity),
|
|
77
|
-
listing_type_id: normalizeString(item.listing_type_id),
|
|
78
|
-
permalink: normalizeString(item.permalink),
|
|
79
|
-
thumbnail: normalizeString(item.thumbnail)
|
|
80
|
-
}))
|
|
234
|
+
items_schema: itemsSchema,
|
|
235
|
+
items: compactItems
|
|
81
236
|
})
|
|
82
237
|
);
|
|
83
238
|
} catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/tools/mercadolibre/search-items.ts"],
|
|
4
|
-
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\nimport {\n buildMercadoLibrePaginationMetadata,\n formatMercadoLibreError,\n normalizeMercadoLibrePaging,\n} from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n getMercadoLibreItemDetailsBatch,\n searchMercadoLibreItems,\n} from \"../../services/mercadolibre/mercadolibre-items.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport { asArray, normalizeString, toNumber } from \"./helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nexport const meliSearchItemsSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n status: z.string().trim().min(1).optional().describe(\"Optional item status filter.\"),\n category: z.string().trim().min(1).optional().describe(\"Optional category id filter.\"),\n listingTypeId: z.string().trim().min(1).optional().describe(\"Optional listing type filter.\"),\n orders: z.string().trim().min(1).optional().describe(\"Optional MercadoLibre sort expression for items search.\"),\n includeDetails: z.boolean().optional().describe(\"Whether to multiget compact item details for returned item ids.\"),\n limit: z.number().int().min(1).max(50).optional().describe(\"Results per page (1-50).\"),\n offset: z.number().int().min(0).max(5000).optional().describe(\"Pagination offset.\"),\n});\n\nexport async function meliSearchItemsHandler(params: z.infer<typeof meliSearchItemsSchema>) {\n try {\n const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);\n if (!profileResolution.ok) {\n return profileResolution.response;\n }\n\n const profileId = profileResolution.value.profileId;\n const access = await getMercadoLibreAccessForProfile(profileId);\n const searchResponse = await searchMercadoLibreItems(profileId, {\n sellerId: access.sellerId,\n status: params.status,\n category: params.category,\n listingTypeId: params.listingTypeId,\n orders: params.orders,\n limit: params.limit ?? 20,\n offset: params.offset ?? 0,\n });\n\n const itemIds = asArray<string>(searchResponse.results).map((value) => normalizeString(value)).filter(Boolean);\n const compactItems = params.includeDetails\n ? await getMercadoLibreItemDetailsBatch(profileId, itemIds.slice(0, 20), [\n \"id\",\n \"title\",\n \"status\",\n \"price\",\n \"currency_id\",\n \"available_quantity\",\n \"sold_quantity\",\n \"listing_type_id\",\n \"permalink\",\n \"thumbnail\",\n ])\n : [];\n\n const paging = normalizeMercadoLibrePaging(searchResponse.paging as Record<string, unknown>);\n\n return object(\n stripNulls({\n profile_id: profileId,\n metadata: buildMercadoLibrePaginationMetadata({\n total: paging.total,\n limit: paging.limit || params.limit || itemIds.length,\n offset: paging.offset || params.offset || 0,\n returned: itemIds.length,\n nextField: \"offset\",\n }),\n item_ids: itemIds,\n items: compactItems.map((item) => ({\n item_id: normalizeString(item.id),\n title: normalizeString(item.title),\n status: normalizeString(item.status),\n price: toNumber(item.price),\n currency_id: normalizeString(item.currency_id, \"UNKNOWN\"),\n available_quantity: toNumber(item.available_quantity),\n sold_quantity: toNumber(item.sold_quantity),\n listing_type_id: normalizeString(item.listing_type_id),\n permalink: normalizeString(item.permalink),\n thumbnail: normalizeString(item.thumbnail),\n })),\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to search MercadoLibre items\"));\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,OAAO,cAAc;AAC9B,SAAS,SAAS;AAElB,SAAS,uCAAuC;AAChD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { error, object } from \"mcp-use/server\";\nimport { z } from \"zod\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\nimport { formatMercadoLibreError, normalizeMercadoLibrePaging } from \"../../services/mercadolibre/mercadolibre-api.js\";\nimport {\n getMercadoLibreItemDetailsBatch,\n getMercadoLibreItemDetailsWithFallbackBatch,\n searchMercadoLibreItems,\n searchMercadoLibreItemsBatch,\n} from \"../../services/mercadolibre/mercadolibre-items.js\";\nimport { stripNulls } from \"../../utils/strip-payload.js\";\nimport { asArray, asRecord, normalizeString, toNumber } from \"./helpers.js\";\nimport { resolveMercadoLibreProfileOrSelection } from \"./profile-resolution.js\";\nimport { mercadolibreProfileIdSchemaField } from \"./write-helpers.js\";\n\nconst DEFAULT_STATUS = \"active\";\nconst SEARCH_PAGE_LIMIT = 50;\nconst DETAIL_BATCH_SIZE = 20;\nconst PAGE_FETCH_CONCURRENCY = 15;\nconst PAGE_FETCH_MAX_RETRIES = 2;\nconst AVAILABLE_STATUS_FILTERS = [\n \"active\",\n \"paused\",\n \"closed\",\n \"under_review\",\n \"inactive\",\n \"not_yet_active\",\n \"payment_required\",\n \"deleted\",\n];\n\nconst itemsSchema = [\n \"item_id\",\n \"title\",\n \"status\",\n \"price\",\n \"currency_id\",\n \"available_quantity\",\n \"sold_quantity\",\n \"listing_type_id\",\n \"category_id\",\n] as const;\n\nexport const meliSearchItemsSchema = z.object({\n profileId: mercadolibreProfileIdSchemaField,\n status: z.string().trim().min(1).optional().describe(\"Optional item status filter. Defaults to active when omitted.\"),\n title: z.string().trim().min(1).optional().describe(\"Optional item title or text query. Maps to MercadoLibre q search.\"),\n category: z.string().trim().min(1).optional().describe(\"Optional category id filter.\"),\n listingTypeId: z.string().trim().min(1).optional().describe(\"Optional listing type filter.\"),\n orders: z.string().trim().min(1).optional().describe(\"Optional MercadoLibre sort expression for items search.\"),\n limit: z.number().int().min(1).max(50).optional().describe(\"Optional maximum detailed items to return. If omitted, returns the full matching universe from offset onward.\"),\n offset: z.number().int().min(0).max(5000).optional().describe(\"Pagination offset over the matching MercadoLibre items universe.\"),\n});\n\nfunction formatCompactItem(item: Record<string, unknown>) {\n return [\n normalizeString(item.id),\n normalizeString(item.title),\n normalizeString(item.status),\n toNumber(item.price),\n normalizeString(item.currency_id, \"UNKNOWN\"),\n toNumber(item.available_quantity),\n toNumber(item.sold_quantity),\n normalizeString(item.listing_type_id),\n normalizeString(item.category_id),\n ];\n}\n\nfunction buildItemsMetadata(params: {\n total: number;\n effectiveLimit: number;\n offset: number;\n returned: number;\n appliedStatus: string;\n usedDefaultStatus: boolean;\n detailFailures: number;\n pagesRequested: number;\n pagesSucceeded: number;\n pagesFailed: number;\n universeFullyFetched: boolean;\n limitWasExplicit: boolean;\n}) {\n const nextOffset = params.offset + params.returned;\n const hasMore = nextOffset < params.total;\n\n let continuation = \"No more pages for this query.\";\n if (params.pagesFailed > 0 || params.detailFailures > 0) {\n continuation = hasMore\n ? `Se recuper\u00F3 cobertura parcial. Reintentar la misma tool con offset=${params.offset} y limit=${params.limitWasExplicit ? params.effectiveLimit : 50}. Luego continuar con offset=${nextOffset} si hace falta.`\n : \"Se recuper\u00F3 cobertura parcial. Reintentar la misma tool con los mismos filtros si hace falta completar la cobertura.\";\n } else if (params.limitWasExplicit && hasMore) {\n continuation = `Para continuar trayendo items, volver a invocar meli_search_items con los mismos filtros, offset=${nextOffset} y limit=${params.effectiveLimit}. Repetir hasta has_more=false.`;\n }\n\n return stripNulls({\n total: params.total,\n limit: params.effectiveLimit,\n offset: params.offset,\n returned: params.returned,\n has_more: params.limitWasExplicit ? hasMore || params.pagesFailed > 0 || params.detailFailures > 0 : params.pagesFailed > 0 || params.detailFailures > 0,\n next_offset: params.limitWasExplicit && hasMore ? nextOffset : undefined,\n continuation,\n applied_status: params.appliedStatus,\n default_status_notice: params.usedDefaultStatus\n ? 'Solo se devolvieron publicaciones active por default. Tambi\u00E9n se puede pedir status expl\u00EDcito como paused, closed, under_review, inactive, not_yet_active, payment_required o deleted.'\n : undefined,\n available_status_filters: AVAILABLE_STATUS_FILTERS,\n detail_coverage: {\n detail_failures: params.detailFailures,\n details_complete: params.detailFailures === 0,\n },\n pages_requested: params.pagesRequested,\n pages_succeeded: params.pagesSucceeded,\n pages_failed: params.pagesFailed,\n universe_fully_fetched: params.universeFullyFetched,\n });\n}\n\nasync function fetchAllMatchingItemIds(profileId: string, sellerId: string, params: z.infer<typeof meliSearchItemsSchema>) {\n const appliedStatus = params.status ?? DEFAULT_STATUS;\n const firstResponse = await searchMercadoLibreItems(profileId, {\n sellerId,\n status: appliedStatus,\n q: params.title,\n category: params.category,\n listingTypeId: params.listingTypeId,\n orders: params.orders,\n limit: SEARCH_PAGE_LIMIT,\n offset: 0,\n });\n\n const firstPaging = normalizeMercadoLibrePaging(asRecord(firstResponse.paging));\n const total = firstPaging.total;\n const effectiveLimit = firstPaging.limit || SEARCH_PAGE_LIMIT;\n const ids = asArray<string>(firstResponse.results).map((value) => normalizeString(value)).filter(Boolean);\n const failedOffsets: number[] = [];\n let pagesRequested = 1;\n let pagesSucceeded = 1;\n\n if (effectiveLimit > 0 && total > ids.length) {\n const nextOffsets: number[] = [];\n for (let nextOffset = effectiveLimit; nextOffset < total; nextOffset += effectiveLimit) {\n nextOffsets.push(nextOffset);\n }\n\n pagesRequested += nextOffsets.length;\n\n if (nextOffsets.length > 0) {\n const batch = await searchMercadoLibreItemsBatch(\n profileId,\n {\n sellerId,\n status: appliedStatus,\n q: params.title,\n category: params.category,\n listingTypeId: params.listingTypeId,\n orders: params.orders,\n limit: effectiveLimit,\n },\n nextOffsets,\n {\n maxConcurrency: PAGE_FETCH_CONCURRENCY,\n maxRetries: PAGE_FETCH_MAX_RETRIES,\n }\n );\n\n pagesSucceeded += batch.successful.length;\n batch.successful.forEach((entry) => {\n ids.push(...asArray<string>(entry.document.results).map((value) => normalizeString(value)).filter(Boolean));\n });\n batch.failed.forEach((failure) => {\n failedOffsets.push(Number(failure.id));\n });\n }\n }\n\n return {\n ids,\n total,\n appliedStatus,\n pagesRequested,\n pagesSucceeded,\n pagesFailed: failedOffsets.length,\n universeFullyFetched: failedOffsets.length === 0 && ids.length >= total,\n };\n}\n\nasync function fetchDetailedItems(profileId: string, itemIds: string[]) {\n const details: Record<string, Record<string, unknown>> = {};\n let detailFailures = 0;\n\n for (let index = 0; index < itemIds.length; index += DETAIL_BATCH_SIZE) {\n const chunk = itemIds.slice(index, index + DETAIL_BATCH_SIZE);\n if (chunk.length === 0) {\n continue;\n }\n\n const batch = await getMercadoLibreItemDetailsBatch(profileId, chunk, [\n \"id\",\n \"title\",\n \"status\",\n \"price\",\n \"currency_id\",\n \"available_quantity\",\n \"sold_quantity\",\n \"listing_type_id\",\n \"category_id\",\n ]);\n\n const foundIds = new Set<string>();\n for (const item of batch) {\n const itemId = normalizeString(item.id);\n if (!itemId) {\n continue;\n }\n foundIds.add(itemId);\n details[itemId] = item;\n }\n\n const missingIds = chunk.filter((itemId) => !foundIds.has(itemId));\n if (missingIds.length > 0) {\n const fallback = await getMercadoLibreItemDetailsWithFallbackBatch(profileId, missingIds, {\n maxConcurrency: 10,\n maxRetries: 1,\n });\n fallback.successful.forEach((entry) => {\n details[entry.id] = entry.document;\n });\n detailFailures += fallback.failed.length;\n }\n }\n\n return {\n orderedItems: itemIds.map((itemId) => details[itemId]).filter(Boolean),\n detailFailures,\n };\n}\n\nexport async function meliSearchItemsHandler(params: z.infer<typeof meliSearchItemsSchema>) {\n try {\n const profileResolution = await resolveMercadoLibreProfileOrSelection(params.profileId);\n if (!profileResolution.ok) {\n return profileResolution.response;\n }\n\n const profileId = profileResolution.value.profileId;\n const access = await getMercadoLibreAccessForProfile(profileId);\n const requestedOffset = params.offset ?? 0;\n const limitWasExplicit = typeof params.limit === \"number\";\n const usedDefaultStatus = !params.status;\n\n const { ids, total, appliedStatus, pagesRequested, pagesSucceeded, pagesFailed, universeFullyFetched } = await fetchAllMatchingItemIds(profileId, access.sellerId, params);\n\n const pageIds = limitWasExplicit\n ? ids.slice(requestedOffset, requestedOffset + (params.limit ?? 0))\n : ids.slice(requestedOffset);\n const effectiveLimit = limitWasExplicit ? params.limit ?? pageIds.length : Math.max(pageIds.length, 0);\n\n const { orderedItems, detailFailures } = await fetchDetailedItems(profileId, pageIds);\n const compactItems = orderedItems.map((item) => formatCompactItem(item));\n\n return object(\n stripNulls({\n profile_id: profileId,\n metadata: buildItemsMetadata({\n total,\n effectiveLimit,\n offset: requestedOffset,\n returned: compactItems.length,\n appliedStatus,\n usedDefaultStatus,\n detailFailures,\n pagesRequested,\n pagesSucceeded,\n pagesFailed,\n universeFullyFetched,\n limitWasExplicit,\n }),\n items_schema: itemsSchema,\n items: compactItems,\n })\n );\n } catch (err) {\n return error(formatMercadoLibreError(err, \"Failed to search MercadoLibre items\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|