daiso 1.0.5 → 1.0.7
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/README.md +101 -7
- package/dist/api/actionsProxy.js +111 -21
- package/dist/api/configStatus.js +23 -0
- package/dist/api/gs25Handlers.js +68 -22
- package/dist/api/handlers.js +32 -3
- package/dist/api/healthChecks.js +233 -20
- package/dist/api/lottemartHandlers.js +17 -1
- package/dist/api/placesHandlers.js +44 -0
- package/dist/api/response.js +2 -0
- package/dist/api/routes/healthRoutes.js +20 -3
- package/dist/api/routes/placesRoutes.js +9 -0
- package/dist/bin.js +0 -0
- package/dist/cli/args.js +21 -0
- package/dist/cli/commandOptions.js +96 -0
- package/dist/cli/commands/convenience.js +73 -1
- package/dist/cli/commands/daiso.js +18 -2
- package/dist/cli/http.js +58 -0
- package/dist/cli/interactiveItemSearch.js +4 -2
- package/dist/cli/interactivePrompt.js +3 -2
- package/dist/cli.js +25 -1
- package/dist/cliHelp.js +20 -6
- package/dist/cliInteractive.js +75 -6
- package/dist/cliPicker.js +1 -0
- package/dist/cliRenderer.js +93 -3
- package/dist/core/errors.js +66 -0
- package/dist/core/registry.js +246 -3
- package/dist/index.js +19 -4
- package/dist/pages/openapiFullSpec.js +3 -0
- package/dist/pages/openapiSpecActions.js +308 -42
- package/dist/pages/openapiSpecPathsCu.js +7 -0
- package/dist/pages/openapiSpecPathsLotteMart.js +14 -0
- package/dist/pages/openapiSpecPathsOliveyoung.js +35 -0
- package/dist/pages/openapiSpecPathsPlaces.js +85 -0
- package/dist/pages/promptReferenceText.js +16 -7
- package/dist/pages/promptRetailCinemaText.js +24 -2
- package/dist/services/daiso/client.js +8 -0
- package/dist/services/daiso/index.js +2 -0
- package/dist/services/daiso/tools/checkInventory.js +4 -2
- package/dist/services/daiso/tools/findInventoryByName.js +222 -0
- package/dist/services/daiso/tools/getDisplayLocation.js +2 -2
- package/dist/services/emart24/client.js +9 -0
- package/dist/services/emart24/tools/searchProducts.js +27 -17
- package/dist/services/gs25/api.js +3 -0
- package/dist/services/gs25/client.js +216 -30
- package/dist/services/gs25/index.js +11 -3
- package/dist/services/gs25/tools/checkInventory.js +39 -12
- package/dist/services/gs25/tools/findNearbyStores.js +19 -5
- package/dist/services/gs25/tools/searchProducts.js +39 -25
- package/dist/services/lottemart/client.js +10 -1
- package/dist/services/lottemart/tools/searchProducts.js +8 -1
- package/dist/services/lottemart/zetta.js +1 -1
- package/dist/services/oliveyoung/client.js +206 -86
- package/dist/services/oliveyoung/tools/checkInventory.js +7 -2
- package/dist/services/places/client.js +116 -0
- package/dist/services/places/index.js +23 -0
- package/dist/services/places/tools/searchNearby.js +56 -0
- package/dist/services/places/types.js +1 -0
- package/dist/services/seveneleven/client.js +7 -0
- package/dist/services/seveneleven/tools/searchProducts.js +29 -18
- package/dist/utils/http.js +82 -10
- package/dist/utils/zyte.js +74 -26
- package/openapi.json +22 -3
- package/openapi.yaml +16 -3
- package/package.json +28 -9
|
@@ -11,6 +11,10 @@ const DAISO_DEFAULT_HEADERS = {
|
|
|
11
11
|
Accept: 'application/json, text/html, */*',
|
|
12
12
|
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
13
13
|
};
|
|
14
|
+
const DAISO_DEFAULT_FETCH_OPTIONS = {
|
|
15
|
+
retries: 1,
|
|
16
|
+
retryDelayMs: 250,
|
|
17
|
+
};
|
|
14
18
|
function withDaisoHeaders(headers = {}) {
|
|
15
19
|
return {
|
|
16
20
|
...DAISO_DEFAULT_HEADERS,
|
|
@@ -19,18 +23,21 @@ function withDaisoHeaders(headers = {}) {
|
|
|
19
23
|
}
|
|
20
24
|
export async function daisoFetch(url, options = {}) {
|
|
21
25
|
return fetchWithTimeout(url, {
|
|
26
|
+
...DAISO_DEFAULT_FETCH_OPTIONS,
|
|
22
27
|
...options,
|
|
23
28
|
headers: withDaisoHeaders(options.headers),
|
|
24
29
|
});
|
|
25
30
|
}
|
|
26
31
|
export async function fetchDaisoJson(url, options = {}) {
|
|
27
32
|
return fetchJson(url, {
|
|
33
|
+
...DAISO_DEFAULT_FETCH_OPTIONS,
|
|
28
34
|
...options,
|
|
29
35
|
headers: withDaisoHeaders(options.headers),
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
38
|
export async function fetchDaisoHtml(url, options = {}) {
|
|
33
39
|
return fetchText(url, {
|
|
40
|
+
...DAISO_DEFAULT_FETCH_OPTIONS,
|
|
34
41
|
...options,
|
|
35
42
|
headers: withDaisoHeaders(options.headers),
|
|
36
43
|
});
|
|
@@ -83,6 +90,7 @@ export async function createDaisoAuthContext() {
|
|
|
83
90
|
export async function fetchDaisoJsonWithAuth(url, options = {}) {
|
|
84
91
|
const auth = await createDaisoAuthContext();
|
|
85
92
|
return fetchJson(url, {
|
|
93
|
+
...DAISO_DEFAULT_FETCH_OPTIONS,
|
|
86
94
|
...options,
|
|
87
95
|
headers: withDaisoHeaders({
|
|
88
96
|
...options.headers,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { createSearchProductsTool } from './tools/searchProducts.js';
|
|
7
7
|
import { createFindStoresTool } from './tools/findStores.js';
|
|
8
8
|
import { createCheckInventoryTool } from './tools/checkInventory.js';
|
|
9
|
+
import { createFindInventoryByNameTool } from './tools/findInventoryByName.js';
|
|
9
10
|
import { createGetPriceInfoTool } from './tools/getPriceInfo.js';
|
|
10
11
|
import { createGetDisplayLocationTool } from './tools/getDisplayLocation.js';
|
|
11
12
|
/**
|
|
@@ -27,6 +28,7 @@ class DaisoService {
|
|
|
27
28
|
createSearchProductsTool(),
|
|
28
29
|
createFindStoresTool(),
|
|
29
30
|
createCheckInventoryTool(),
|
|
31
|
+
createFindInventoryByNameTool(),
|
|
30
32
|
createGetPriceInfoTool(),
|
|
31
33
|
createGetDisplayLocationTool(),
|
|
32
34
|
];
|
|
@@ -142,9 +142,11 @@ export function createCheckInventoryTool() {
|
|
|
142
142
|
name: 'daiso_check_inventory',
|
|
143
143
|
metadata: {
|
|
144
144
|
title: '재고 확인',
|
|
145
|
-
description: '특정 제품의 매장별 재고를 확인합니다. 매장명/주소 검색 또는 위치 기반으로 조회합니다.',
|
|
145
|
+
description: '특정 제품의 매장별 재고를 확인합니다. 상품명만 아는 경우 daiso_search_products로 후보를 먼저 찾은 뒤 productId를 전달하세요. 매장명/주소 검색 또는 위치 기반으로 조회합니다. "근처/주변"처럼 위치가 모호하면 storeQuery, latitude/longitude 또는 사용자 위치를 먼저 확인하세요.',
|
|
146
146
|
inputSchema: {
|
|
147
|
-
productId: z
|
|
147
|
+
productId: z
|
|
148
|
+
.string()
|
|
149
|
+
.describe('제품 ID. 상품명만 알면 먼저 daiso_search_products로 상품의 id를 확인하세요.'),
|
|
148
150
|
storeQuery: z.string().optional().describe('매장 검색어 (매장명 또는 주소, 예: 안산 중앙역)'),
|
|
149
151
|
latitude: z.number().optional().default(37.5665).describe('위도 (기본값: 서울 시청 37.5665)'),
|
|
150
152
|
longitude: z.number().optional().default(126.978).describe('경도 (기본값: 서울 시청 126.978)'),
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 상품명 기반 재고 통합 조회 도구
|
|
3
|
+
*
|
|
4
|
+
* 상품명과 위치만 아는 사용자를 위해 상품 검색과 재고 조회를 한 번에 수행합니다.
|
|
5
|
+
*/
|
|
6
|
+
import * as z from 'zod';
|
|
7
|
+
import { fetchProducts } from './searchProducts.js';
|
|
8
|
+
import { fetchOnlineStock, fetchStoreInventory } from './checkInventory.js';
|
|
9
|
+
import { fetchStores } from './findStores.js';
|
|
10
|
+
function buildTextResponse(payload) {
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
13
|
+
structuredContent: payload,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function buildEmptySummary(query, storeQuery) {
|
|
17
|
+
return {
|
|
18
|
+
headline: `"${query}" 상품 후보를 찾지 못했습니다.`,
|
|
19
|
+
selectedProduct: '',
|
|
20
|
+
storeQuery: storeQuery || '미지정',
|
|
21
|
+
inventorySummary: '상품 후보가 없어 온라인/매장 재고 조회를 건너뛰었습니다.',
|
|
22
|
+
displayLocationHint: '상품 후보를 먼저 확인한 뒤 daiso_get_display_location을 사용할 수 있습니다.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function resolveInventoryLocation(args) {
|
|
26
|
+
if (typeof args.latitude === 'number' && typeof args.longitude === 'number') {
|
|
27
|
+
return {
|
|
28
|
+
latitude: args.latitude,
|
|
29
|
+
longitude: args.longitude,
|
|
30
|
+
source: 'input',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (args.storeQuery.trim().length > 0) {
|
|
34
|
+
const stores = await fetchStores(args.storeQuery);
|
|
35
|
+
const firstStore = stores[0];
|
|
36
|
+
if (firstStore) {
|
|
37
|
+
return {
|
|
38
|
+
latitude: firstStore.lat,
|
|
39
|
+
longitude: firstStore.lng,
|
|
40
|
+
source: 'storeQuery',
|
|
41
|
+
storeName: firstStore.name,
|
|
42
|
+
storeAddress: firstStore.address,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
latitude: args.latitude ?? 37.5665,
|
|
48
|
+
longitude: args.longitude ?? 126.978,
|
|
49
|
+
source: 'default',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildInventorySummary(args) {
|
|
53
|
+
const storeScope = args.storeQuery || '기본 위치 주변';
|
|
54
|
+
return {
|
|
55
|
+
headline: `"${args.query}" 조회 결과, "${args.selectedProductName}" 상품을 기준으로 재고를 확인했습니다.`,
|
|
56
|
+
selectedProduct: args.selectedProductName,
|
|
57
|
+
storeQuery: args.storeQuery || '미지정',
|
|
58
|
+
inventorySummary: `${storeScope} 매장 ${args.totalStores}곳 중 ${args.inStockCount}곳에서 재고가 확인되었습니다. 온라인 재고는 ${args.onlineStock}개입니다.`,
|
|
59
|
+
displayLocationHint: '진열 위치가 필요하면 storeInventory.stores[].storeCode로 daiso_get_display_location을 호출하세요.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function toSummary(product) {
|
|
63
|
+
return {
|
|
64
|
+
id: product.id,
|
|
65
|
+
name: product.name,
|
|
66
|
+
imageUrl: product.imageUrl,
|
|
67
|
+
brand: product.brand,
|
|
68
|
+
soldOut: product.soldOut,
|
|
69
|
+
isNew: product.isNew,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function normalizeProductName(value) {
|
|
73
|
+
return value.toLowerCase().replace(/\s+/g, '');
|
|
74
|
+
}
|
|
75
|
+
function scoreProductCandidate(query, candidate, index) {
|
|
76
|
+
const normalizedQuery = normalizeProductName(query);
|
|
77
|
+
const normalizedName = normalizeProductName(candidate.name);
|
|
78
|
+
const exactMatchScore = normalizedName === normalizedQuery ? 100 : 0;
|
|
79
|
+
const availabilityScore = candidate.soldOut ? 0 : 20;
|
|
80
|
+
const startsWithScore = normalizedName.startsWith(normalizedQuery) ? 10 : 0;
|
|
81
|
+
const containsScore = normalizedName.includes(normalizedQuery) ? 5 : 0;
|
|
82
|
+
return exactMatchScore + availabilityScore + startsWithScore + containsScore - index / 1000;
|
|
83
|
+
}
|
|
84
|
+
function selectProductCandidate(query, candidates) {
|
|
85
|
+
return (candidates
|
|
86
|
+
.map((candidate, index) => ({
|
|
87
|
+
candidate,
|
|
88
|
+
score: scoreProductCandidate(query, candidate, index),
|
|
89
|
+
}))
|
|
90
|
+
.sort((a, b) => b.score - a.score)[0]?.candidate ?? null);
|
|
91
|
+
}
|
|
92
|
+
async function findInventoryByName(args) {
|
|
93
|
+
const { query, storeQuery = '', latitude, longitude, page = 1, pageSize = 30, productLimit = 5, } = args;
|
|
94
|
+
if (!query || query.trim().length === 0) {
|
|
95
|
+
throw new Error('상품명(query)을 입력해주세요.');
|
|
96
|
+
}
|
|
97
|
+
const productResult = await fetchProducts(query, 1, productLimit);
|
|
98
|
+
const productCandidates = productResult.products.map(toSummary);
|
|
99
|
+
const selectedProduct = selectProductCandidate(query, productCandidates);
|
|
100
|
+
if (!selectedProduct) {
|
|
101
|
+
return buildTextResponse({
|
|
102
|
+
query,
|
|
103
|
+
storeQuery,
|
|
104
|
+
summary: buildEmptySummary(query, storeQuery),
|
|
105
|
+
productCandidates: [],
|
|
106
|
+
selectedProduct: null,
|
|
107
|
+
onlineStock: 0,
|
|
108
|
+
storeInventory: {
|
|
109
|
+
totalStores: 0,
|
|
110
|
+
inStockCount: 0,
|
|
111
|
+
outOfStockCount: 0,
|
|
112
|
+
page,
|
|
113
|
+
pageSize,
|
|
114
|
+
stores: [],
|
|
115
|
+
},
|
|
116
|
+
nextSteps: {
|
|
117
|
+
productSearchTool: 'daiso_search_products',
|
|
118
|
+
inventoryTool: 'daiso_check_inventory',
|
|
119
|
+
note: '상품 후보가 없어서 재고 조회를 진행하지 않았습니다.',
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const resolvedLocation = await resolveInventoryLocation({
|
|
124
|
+
storeQuery,
|
|
125
|
+
latitude,
|
|
126
|
+
longitude,
|
|
127
|
+
});
|
|
128
|
+
const [onlineStock, storeResult] = await Promise.all([
|
|
129
|
+
fetchOnlineStock(selectedProduct.id),
|
|
130
|
+
fetchStoreInventory(selectedProduct.id, resolvedLocation.latitude, resolvedLocation.longitude, page, pageSize, storeQuery),
|
|
131
|
+
]);
|
|
132
|
+
const stores = storeResult.stores;
|
|
133
|
+
const inStockCount = stores.filter((store) => store.quantity > 0).length;
|
|
134
|
+
const result = {
|
|
135
|
+
query,
|
|
136
|
+
storeQuery,
|
|
137
|
+
location: resolvedLocation,
|
|
138
|
+
summary: buildInventorySummary({
|
|
139
|
+
query,
|
|
140
|
+
storeQuery,
|
|
141
|
+
selectedProductName: selectedProduct.name,
|
|
142
|
+
onlineStock,
|
|
143
|
+
inStockCount,
|
|
144
|
+
totalStores: storeResult.totalCount,
|
|
145
|
+
}),
|
|
146
|
+
productCandidates,
|
|
147
|
+
selectedProduct,
|
|
148
|
+
onlineStock,
|
|
149
|
+
storeInventory: {
|
|
150
|
+
totalStores: storeResult.totalCount,
|
|
151
|
+
inStockCount,
|
|
152
|
+
outOfStockCount: stores.filter((store) => store.quantity === 0).length,
|
|
153
|
+
page,
|
|
154
|
+
pageSize,
|
|
155
|
+
stores,
|
|
156
|
+
},
|
|
157
|
+
nextSteps: {
|
|
158
|
+
productSearchTool: 'daiso_search_products',
|
|
159
|
+
inventoryTool: 'daiso_check_inventory',
|
|
160
|
+
displayLocationTool: 'daiso_get_display_location',
|
|
161
|
+
storeCodeSource: '진열 위치가 필요하면 storeInventory.stores[].storeCode를 사용하세요.',
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
return buildTextResponse(result);
|
|
165
|
+
}
|
|
166
|
+
const inventoryByNameOutputSchema = {
|
|
167
|
+
query: z.string().describe('사용자가 입력한 상품 검색어'),
|
|
168
|
+
storeQuery: z.string().describe('사용자가 입력한 위치 키워드'),
|
|
169
|
+
location: z
|
|
170
|
+
.object({
|
|
171
|
+
latitude: z.number(),
|
|
172
|
+
longitude: z.number(),
|
|
173
|
+
source: z.enum(['input', 'storeQuery', 'default']),
|
|
174
|
+
storeName: z.string().optional(),
|
|
175
|
+
storeAddress: z.string().optional(),
|
|
176
|
+
})
|
|
177
|
+
.optional()
|
|
178
|
+
.describe('재고 조회에 사용한 위치와 위치 해석 출처'),
|
|
179
|
+
summary: z
|
|
180
|
+
.object({
|
|
181
|
+
headline: z.string(),
|
|
182
|
+
selectedProduct: z.string(),
|
|
183
|
+
storeQuery: z.string(),
|
|
184
|
+
inventorySummary: z.string(),
|
|
185
|
+
displayLocationHint: z.string(),
|
|
186
|
+
})
|
|
187
|
+
.describe('사용자에게 바로 보여줄 요약'),
|
|
188
|
+
productCandidates: z.array(z.unknown()).describe('검색된 상품 후보 목록'),
|
|
189
|
+
selectedProduct: z.unknown().nullable().describe('재고 조회 기준으로 선택한 상품'),
|
|
190
|
+
onlineStock: z.number().describe('온라인 재고 수량'),
|
|
191
|
+
storeInventory: z
|
|
192
|
+
.object({
|
|
193
|
+
totalStores: z.number(),
|
|
194
|
+
inStockCount: z.number(),
|
|
195
|
+
outOfStockCount: z.number(),
|
|
196
|
+
page: z.number(),
|
|
197
|
+
pageSize: z.number(),
|
|
198
|
+
stores: z.array(z.unknown()),
|
|
199
|
+
})
|
|
200
|
+
.describe('매장별 재고 조회 결과'),
|
|
201
|
+
nextSteps: z.record(z.string(), z.unknown()).describe('후속 도구 호출 안내'),
|
|
202
|
+
};
|
|
203
|
+
export function createFindInventoryByNameTool() {
|
|
204
|
+
return {
|
|
205
|
+
name: 'daiso_find_inventory_by_name',
|
|
206
|
+
metadata: {
|
|
207
|
+
title: '상품명 기반 재고 통합 조회',
|
|
208
|
+
description: '상품명과 대강의 위치만 아는 사용자를 위해 제품 검색부터 재고 조회까지 한 번에 수행합니다. 결과의 storeInventory.stores[].storeCode는 진열 위치 조회에 사용할 수 있습니다.',
|
|
209
|
+
inputSchema: {
|
|
210
|
+
query: z.string().describe('검색할 상품명 또는 키워드'),
|
|
211
|
+
storeQuery: z.string().optional().describe('역명, 동네, 매장명 같은 대강의 위치'),
|
|
212
|
+
latitude: z.number().optional().describe('위도 (생략 시 서울 시청 37.5665)'),
|
|
213
|
+
longitude: z.number().optional().describe('경도 (생략 시 서울 시청 126.978)'),
|
|
214
|
+
page: z.number().optional().default(1).describe('재고 매장 페이지 번호 (기본값: 1)'),
|
|
215
|
+
pageSize: z.number().optional().default(30).describe('재고 매장 페이지 크기 (기본값: 30)'),
|
|
216
|
+
productLimit: z.number().optional().default(5).describe('상품 후보 수 (기본값: 5)'),
|
|
217
|
+
},
|
|
218
|
+
outputSchema: inventoryByNameOutputSchema,
|
|
219
|
+
},
|
|
220
|
+
handler: findInventoryByName,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -62,12 +62,12 @@ export function createGetDisplayLocationTool() {
|
|
|
62
62
|
name: 'daiso_get_display_location',
|
|
63
63
|
metadata: {
|
|
64
64
|
title: '진열 위치 조회',
|
|
65
|
-
description: '다이소 매장 내 상품의 진열 위치(구역/층)를 조회합니다. 상품 ID와 매장 코드가 필요합니다.',
|
|
65
|
+
description: '다이소 매장 내 상품의 진열 위치(구역/층)를 조회합니다. 상품 ID와 매장 코드가 필요합니다. storeCode를 모르면 먼저 daiso_check_inventory로 재고를 조회하고 storeInventory.stores[].storeCode를 사용하세요.',
|
|
66
66
|
inputSchema: {
|
|
67
67
|
productId: z.string().describe('상품 ID (daiso_search_products로 조회한 상품의 id)'),
|
|
68
68
|
storeCode: z
|
|
69
69
|
.string()
|
|
70
|
-
.describe('매장
|
|
70
|
+
.describe('매장 코드. storeCode를 모르면 먼저 daiso_check_inventory 결과의 storeInventory.stores[].storeCode를 확인하세요.'),
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
handler: getDisplayLocation,
|
|
@@ -12,6 +12,10 @@ const EMART24_FORM_HEADERS = {
|
|
|
12
12
|
Accept: 'application/json, text/javascript, */*; q=0.01',
|
|
13
13
|
'X-Requested-With': 'XMLHttpRequest',
|
|
14
14
|
};
|
|
15
|
+
const EMART24_DEFAULT_FETCH_OPTIONS = {
|
|
16
|
+
retries: 1,
|
|
17
|
+
retryDelayMs: 250,
|
|
18
|
+
};
|
|
15
19
|
function buildKeywordVariants(keyword) {
|
|
16
20
|
const trimmed = keyword.trim();
|
|
17
21
|
if (trimmed.length === 0) {
|
|
@@ -101,6 +105,7 @@ export async function fetchEmart24Stores(params = {}, options = {}) {
|
|
|
101
105
|
endpoint.searchParams.set('SVR_24', '1');
|
|
102
106
|
}
|
|
103
107
|
const body = await fetchJson(endpoint.toString(), {
|
|
108
|
+
...EMART24_DEFAULT_FETCH_OPTIONS,
|
|
104
109
|
method: 'GET',
|
|
105
110
|
timeout,
|
|
106
111
|
headers: EMART24_JSON_HEADERS,
|
|
@@ -131,7 +136,9 @@ export async function searchEmart24Products(params, options = {}) {
|
|
|
131
136
|
form.set('saleProductYn', saleProductYn);
|
|
132
137
|
form.set('searchWord', keyword.trim());
|
|
133
138
|
const body = await fetchJson(`${EMART24_API.EVERSE_BASE_URL}${EMART24_API.PRODUCT_SEARCH_PATH}`, {
|
|
139
|
+
...EMART24_DEFAULT_FETCH_OPTIONS,
|
|
134
140
|
method: 'POST',
|
|
141
|
+
retryUnsafeMethods: true,
|
|
135
142
|
timeout,
|
|
136
143
|
headers: EMART24_FORM_HEADERS,
|
|
137
144
|
body: form.toString(),
|
|
@@ -158,6 +165,7 @@ export async function searchEmart24StockByStores(params, options = {}) {
|
|
|
158
165
|
endpoint.searchParams.set('searchPluCode', pluCd);
|
|
159
166
|
endpoint.searchParams.set('bizNoArr', filteredBizNos.join(','));
|
|
160
167
|
return fetchJson(endpoint.toString(), {
|
|
168
|
+
...EMART24_DEFAULT_FETCH_OPTIONS,
|
|
161
169
|
method: 'GET',
|
|
162
170
|
timeout,
|
|
163
171
|
headers: EMART24_JSON_HEADERS,
|
|
@@ -166,6 +174,7 @@ export async function searchEmart24StockByStores(params, options = {}) {
|
|
|
166
174
|
export async function fetchEmart24StoreDetail(bizNo, options = {}) {
|
|
167
175
|
const { timeout = 15000 } = options;
|
|
168
176
|
return fetchJson(`${EMART24_API.EVERSE_BASE_URL}${EMART24_API.STORE_DETAIL_PATH_PREFIX}${encodeURIComponent(bizNo)}`, {
|
|
177
|
+
...EMART24_DEFAULT_FETCH_OPTIONS,
|
|
169
178
|
method: 'GET',
|
|
170
179
|
timeout,
|
|
171
180
|
headers: EMART24_JSON_HEADERS,
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as z from 'zod';
|
|
5
5
|
import { searchEmart24Products } from '../client.js';
|
|
6
|
+
function buildTextResponse(payload) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
9
|
+
structuredContent: payload,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
6
12
|
async function searchProducts(args) {
|
|
7
13
|
const { keyword, page = 1, pageSize = 10, sortType = 'SALE', saleProductYn = 'N', timeoutMs = 15000, } = args;
|
|
8
14
|
if (!keyword || keyword.trim().length === 0) {
|
|
@@ -17,24 +23,27 @@ async function searchProducts(args) {
|
|
|
17
23
|
}, {
|
|
18
24
|
timeout: timeoutMs,
|
|
19
25
|
});
|
|
20
|
-
return {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
totalCount: result.totalCount,
|
|
31
|
-
count: result.products.length,
|
|
32
|
-
products: result.products,
|
|
33
|
-
}, null, 2),
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
};
|
|
26
|
+
return buildTextResponse({
|
|
27
|
+
keyword,
|
|
28
|
+
page,
|
|
29
|
+
pageSize,
|
|
30
|
+
sortType,
|
|
31
|
+
saleProductYn,
|
|
32
|
+
totalCount: result.totalCount,
|
|
33
|
+
count: result.products.length,
|
|
34
|
+
products: result.products,
|
|
35
|
+
});
|
|
37
36
|
}
|
|
37
|
+
const searchProductsOutputSchema = {
|
|
38
|
+
keyword: z.string().describe('검색어'),
|
|
39
|
+
page: z.number().describe('조회 페이지'),
|
|
40
|
+
pageSize: z.number().describe('페이지당 결과 수'),
|
|
41
|
+
sortType: z.string().describe('정렬 기준'),
|
|
42
|
+
saleProductYn: z.string().describe('행사 상품 여부 필터'),
|
|
43
|
+
totalCount: z.number().describe('검색된 전체 상품 수'),
|
|
44
|
+
count: z.number().describe('반환된 상품 수'),
|
|
45
|
+
products: z.array(z.unknown()).describe('이마트24 상품 검색 결과'),
|
|
46
|
+
};
|
|
38
47
|
export function createSearchProductsTool() {
|
|
39
48
|
return {
|
|
40
49
|
name: 'emart24_search_products',
|
|
@@ -57,6 +66,7 @@ export function createSearchProductsTool() {
|
|
|
57
66
|
.describe('행사 상품 여부 필터 (기본값: N)'),
|
|
58
67
|
timeoutMs: z.number().optional().default(15000).describe('요청 제한 시간(ms, 기본값: 15000)'),
|
|
59
68
|
},
|
|
69
|
+
outputSchema: searchProductsOutputSchema,
|
|
60
70
|
},
|
|
61
71
|
handler: searchProducts,
|
|
62
72
|
};
|
|
@@ -6,4 +6,7 @@ export const GS25_API = {
|
|
|
6
6
|
STORE_STOCK_PATH: '/api/bff/v2/store/stock',
|
|
7
7
|
APIGW_BASE_URL: 'https://b2c-apigw.woodongs.com',
|
|
8
8
|
TOTAL_SEARCH_PATH: '/search/v3/totalSearch',
|
|
9
|
+
WEB_BASE_URL: 'https://gs25.gsretail.com',
|
|
10
|
+
WEB_LOCATIONS_PATH: '/gscvs/ko/store-services/locations',
|
|
11
|
+
WEB_LOCATION_LIST_PATH: '/gscvs/ko/store-services/locationList',
|
|
9
12
|
};
|