/**
* scout_inventory — Product search/discovery entry point for AI agents.
*
* Queries the Shopify Storefront API via StorefrontAPI, applies optional
* category and price-range filters, and returns a structured result set.
*/
import type { CatalogProduct } from '../types.js';
import { loadConfig } from '../types.js';
import { ShopifyClient } from '../shopify/client.js';
import { StorefrontAPI } from '../shopify/storefront.js';
import { logger } from '../utils/logger.js';
// ─── Parameter / Result Interfaces ───
export interface ScoutParams {
query: string;
category?: string;
price_min?: number; // minor units (cents)
price_max?: number; // minor units (cents)
limit?: number;
}
export interface ScoutResult {
products: CatalogProduct[];
total_count: number;
has_more: boolean;
}
// ─── Implementation ───
/**
* Search the Shopify catalog for products matching query, category, and price filters.
*
* 1. Builds a Shopify-compatible search query string (combining free-text query
* with `product_type:` filter when a category is specified).
* 2. Calls the Storefront API to retrieve matching products.
* 3. Applies client-side price filtering (price_min / price_max in minor units)
* against variant prices.
* 4. Returns a structured envelope with products, count, and pagination hint.
*/
export async function scoutInventory(params: ScoutParams): Promise<ScoutResult> {
const effectiveLimit = params.limit ?? 10;
try {
// 1. Instantiate Shopify client from environment config
const config = loadConfig();
const client = new ShopifyClient({
storeDomain: config.shopify.storeDomain,
accessToken: config.shopify.accessToken,
storefrontToken: config.shopify.storefrontToken,
});
const storefront = new StorefrontAPI(client);
// 2. Build the Shopify search query
const queryParts: string[] = [params.query];
if (params.category) {
queryParts.push(`product_type:${params.category}`);
}
const searchQuery = queryParts.join(' ');
// Request extra results so we still have enough after price filtering
const fetchLimit = (params.price_min !== undefined || params.price_max !== undefined)
? Math.min(effectiveLimit * 3, 250) // Storefront API caps at 250
: effectiveLimit;
// 3. Query the Storefront API
const products = await storefront.searchProducts(searchQuery, fetchLimit);
// 4. Apply price filtering on variant prices (minor units)
const filtered = products.filter((product) => {
// A product passes the price filter when at least one of its variants
// falls within the requested range.
return product.variants.some((variant) => {
if (params.price_min !== undefined && variant.price < params.price_min) {
return false;
}
if (params.price_max !== undefined && variant.price > params.price_max) {
return false;
}
return true;
});
});
// 5. Trim to requested limit and build result
const trimmed = filtered.slice(0, effectiveLimit);
const hasMore = filtered.length > effectiveLimit;
return {
products: trimmed,
total_count: trimmed.length,
has_more: hasMore,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error('scout_inventory failed', { error: message, params });
return {
products: [],
total_count: 0,
has_more: false,
};
}
}