@samesake/core 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @samesake/core
2
2
 
3
- Config-as-code DSL for commerce search and entity resolution. Declare `collection()` and `entity()` with compile-time validation.
3
+ Config-as-code DSL for Samesake, a TypeScript-first visual-commerce search engine compiler starting with fashion. Declare `collection()` for multi-space commerce search and `entity()` for entity resolution, both with compile-time validation.
4
4
 
5
5
  ```bash
6
6
  bun add @samesake/core
package/dist/index.cjs CHANGED
@@ -28,7 +28,13 @@ __export(index_exports, {
28
28
  collection: () => collection,
29
29
  entity: () => entity,
30
30
  f: () => f,
31
+ fashionAttributeSchema: () => fashionAttributeSchema,
32
+ fashionAttributes: () => fashionAttributes,
33
+ fashionEnrichmentPreset: () => fashionEnrichmentPreset,
34
+ fashionSearchPreset: () => fashionSearchPreset,
31
35
  fields: () => fields,
36
+ isCollectionDef: () => isCollectionDef,
37
+ isEntityDef: () => isEntityDef,
32
38
  pipeline: () => pipeline,
33
39
  s: () => s,
34
40
  sources: () => sources,
@@ -72,6 +78,22 @@ function assertNoIdentCollisions(names, kind) {
72
78
  }
73
79
 
74
80
  // src/index.ts
81
+ var DEF_KIND = /* @__PURE__ */ Symbol.for("@samesake/core.defKind");
82
+ function brandDef(def, kind) {
83
+ Object.defineProperty(def, DEF_KIND, {
84
+ value: kind,
85
+ enumerable: false,
86
+ configurable: false,
87
+ writable: false
88
+ });
89
+ return def;
90
+ }
91
+ function isEntityDef(value) {
92
+ return !!value && typeof value === "object" && value[DEF_KIND] === "entity";
93
+ }
94
+ function isCollectionDef(value) {
95
+ return !!value && typeof value === "object" && value[DEF_KIND] === "collection";
96
+ }
75
97
  var fields = {
76
98
  text(opts = {}) {
77
99
  return { type: "text", ...opts };
@@ -110,7 +132,7 @@ function entity(name, def) {
110
132
  assertIdent(name, "entity");
111
133
  for (const s2 of def.scopes ?? []) assertIdent(s2, "scope");
112
134
  assertNoIdentCollisions(Object.keys(def.fields ?? {}), "field");
113
- return { ...def, name };
135
+ return brandDef({ ...def, name }, "entity");
114
136
  }
115
137
  var f = {
116
138
  text(opts = {}) {
@@ -186,7 +208,7 @@ function collection(name, def) {
186
208
  if (def.spaces && Object.keys(def.spaces).length > 0) {
187
209
  validateSpaceDims(def.spaces);
188
210
  }
189
- return { ...def, name };
211
+ return brandDef({ ...def, name }, "collection");
190
212
  }
191
213
  function stage(name, def) {
192
214
  return { name, ...def };
@@ -194,6 +216,176 @@ function stage(name, def) {
194
216
  function pipeline(...stages) {
195
217
  return { stages };
196
218
  }
219
+ var fashionAttributes = {
220
+ categories: [
221
+ "dresses",
222
+ "tops",
223
+ "bottoms",
224
+ "outerwear",
225
+ "ethnic",
226
+ "activewear",
227
+ "footwear",
228
+ "bags",
229
+ "jewelry",
230
+ "accessories",
231
+ "kids",
232
+ "other"
233
+ ],
234
+ colors: [
235
+ "black",
236
+ "white",
237
+ "ivory",
238
+ "beige",
239
+ "brown",
240
+ "tan",
241
+ "grey",
242
+ "red",
243
+ "pink",
244
+ "purple",
245
+ "blue",
246
+ "navy",
247
+ "green",
248
+ "yellow",
249
+ "orange",
250
+ "multicolor"
251
+ ],
252
+ materials: [
253
+ "cotton",
254
+ "linen",
255
+ "denim",
256
+ "silk",
257
+ "satin",
258
+ "chiffon",
259
+ "knit",
260
+ "polyester",
261
+ "leather",
262
+ "wool",
263
+ "blend",
264
+ "unknown"
265
+ ],
266
+ patterns: ["solid", "floral", "striped", "checked", "embroidered", "graphic", "other"],
267
+ fit: ["slim", "regular", "relaxed", "oversized", "tailored", "unknown"],
268
+ occasions: ["everyday", "office", "party", "wedding guest", "festive", "beach", "gym", "evening"],
269
+ seasons: ["spring", "summer", "fall", "winter", "all-season"],
270
+ formality: ["casual", "smart-casual", "formal", "occasion"],
271
+ modesty: ["modest", "moderate", "revealing"],
272
+ genders: ["women", "men", "unisex", "kids"],
273
+ styles: ["casual", "formal", "bohemian", "minimalist", "streetwear", "romantic", "classic", "sporty"]
274
+ };
275
+ function fashionAttributeSchema() {
276
+ return {
277
+ type: "OBJECT",
278
+ properties: {
279
+ category: { type: "STRING", enum: fashionAttributes.categories },
280
+ silhouette: { type: "STRING" },
281
+ colors: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.colors } },
282
+ material: { type: "STRING", enum: fashionAttributes.materials },
283
+ pattern: { type: "STRING", enum: fashionAttributes.patterns },
284
+ fit: { type: "STRING", enum: fashionAttributes.fit },
285
+ sleeve_length: { type: "STRING" },
286
+ neckline: { type: "STRING" },
287
+ length: { type: "STRING" },
288
+ occasions: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.occasions } },
289
+ season: { type: "STRING", enum: fashionAttributes.seasons },
290
+ formality: { type: "STRING", enum: fashionAttributes.formality },
291
+ modesty: { type: "STRING", enum: fashionAttributes.modesty },
292
+ gender: { type: "STRING", enum: fashionAttributes.genders },
293
+ style_archetypes: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.styles } },
294
+ search_document: { type: "STRING" },
295
+ confidence: { type: "NUMBER" },
296
+ uncertain_fields: { type: "ARRAY", items: { type: "STRING" } }
297
+ },
298
+ required: ["category", "colors", "search_document", "confidence"]
299
+ };
300
+ }
301
+ function fashionEnrichmentPreset(opts = {}) {
302
+ const imageField = opts.imageField ?? "image_url";
303
+ const titleField = opts.titleField ?? "title";
304
+ const descriptionField = opts.descriptionField ?? "description";
305
+ return pipeline(
306
+ stage("fashion_attributes", {
307
+ model: opts.model,
308
+ images: (ctx) => {
309
+ const url = ctx.data[imageField];
310
+ return typeof url === "string" && url ? [url] : [];
311
+ },
312
+ prompt: (ctx) => [
313
+ "Extract structured fashion catalog attributes for visual search.",
314
+ `Title: ${String(ctx.data[titleField] ?? "")}`,
315
+ `Description: ${String(ctx.data[descriptionField] ?? "").slice(0, 1200)}`,
316
+ "Prefer observable image evidence, use allowed enum values, and write a concise shopper-facing search_document."
317
+ ].join("\n"),
318
+ schema: () => fashionAttributeSchema()
319
+ })
320
+ );
321
+ }
322
+ function fashionSearchPreset(opts) {
323
+ const fieldsMap = {
324
+ title: opts.fields?.title ?? "title",
325
+ brand: opts.fields?.brand ?? "brand",
326
+ price: opts.fields?.price ?? "price",
327
+ availability: opts.fields?.availability ?? "available",
328
+ imageUrl: opts.fields?.imageUrl ?? "image_url",
329
+ category: opts.fields?.category ?? "category"
330
+ };
331
+ const spaces = {
332
+ intent: s.text({
333
+ source: "$enriched.search_document $title",
334
+ model: opts.textModel,
335
+ dim: opts.textDim,
336
+ taskType: "RETRIEVAL_DOCUMENT"
337
+ }),
338
+ price: s.number({ field: "price", mode: "closer", dims: 8, min: 0, max: 1e5, scale: "log" })
339
+ };
340
+ if (opts.enableVisual !== false && opts.imageModel && opts.imageDim) {
341
+ spaces.visual = s.image({
342
+ source: `$${fieldsMap.imageUrl}`,
343
+ model: opts.imageModel,
344
+ dim: opts.imageDim,
345
+ taskType: "RETRIEVAL_DOCUMENT"
346
+ });
347
+ }
348
+ return collection(opts.name ?? "products", {
349
+ fields: {
350
+ title: f.text({ searchable: true, path: fieldsMap.title }),
351
+ brand: f.text({ filterable: true, facet: true, path: fieldsMap.brand }),
352
+ price: f.number({ filterable: true, facet: "range", budget: true, path: fieldsMap.price }),
353
+ available: f.boolean({ filterable: true, facet: true, path: fieldsMap.availability }),
354
+ category: f.text({ filterable: true, facet: true, path: fieldsMap.category }),
355
+ colors: f.array(f.enum(fashionAttributes.colors), { filterable: true, soft: true, path: "enriched.colors" }),
356
+ material: f.enum(fashionAttributes.materials, { filterable: true, soft: true, path: "enriched.material" }),
357
+ fit: f.enum(fashionAttributes.fit, { filterable: true, soft: true, path: "enriched.fit" }),
358
+ styles: f.array(f.enum(fashionAttributes.styles), { filterable: true, soft: true, path: "enriched.style_archetypes" })
359
+ },
360
+ enrich: fashionEnrichmentPreset({ model: opts.enrichmentModel, imageField: fieldsMap.imageUrl, titleField: fieldsMap.title }),
361
+ embeddings: {
362
+ intent: {
363
+ source: "$enriched.search_document $title",
364
+ model: opts.textModel,
365
+ dim: opts.textDim,
366
+ taskType: "RETRIEVAL_DOCUMENT"
367
+ }
368
+ },
369
+ spaces,
370
+ search: {
371
+ channels: [
372
+ Channels.fts({ fields: ["title"], weight: 1 }),
373
+ Channels.cosine({ embedding: "intent", weight: 1 }),
374
+ Channels.spaces({ weight: 1 })
375
+ ],
376
+ combiner: "rrf",
377
+ defaultSpaceWeights: {
378
+ intent: 1,
379
+ price: 0.4,
380
+ ...spaces.visual ? { visual: 1.2 } : {}
381
+ },
382
+ nlq: {
383
+ semanticRewrite: true,
384
+ schema: fashionAttributeSchema()
385
+ }
386
+ }
387
+ });
388
+ }
197
389
  var sources = {
198
390
  shopifyFeed(opts) {
199
391
  return {
@@ -235,7 +427,13 @@ var sources = {
235
427
  collection,
236
428
  entity,
237
429
  f,
430
+ fashionAttributeSchema,
431
+ fashionAttributes,
432
+ fashionEnrichmentPreset,
433
+ fashionSearchPreset,
238
434
  fields,
435
+ isCollectionDef,
436
+ isEntityDef,
239
437
  pipeline,
240
438
  s,
241
439
  sources,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FtsChannel, CosineChannel, RecencyChannel, SpacesChannel, CosineScorer, TrigramScorer, PhoneticEqScorer, PhoneExactScorer, AliasHitScorer, InternalCodeExactScorer, SizeUnitGateScorer, BrandGateScorer, CollectionFieldDef, CollectionEmbeddingDef, SpaceDef, PipelineDef, ConnectorDef, TypedSearchChannel, CollectionDef, FieldDef, EmbeddingDef, PhoneticDef, ParseDef, TypedScorer, EntityDef, CollectionTextFieldDef, CollectionNumberFieldDef, CollectionBooleanFieldDef, CollectionEnumFieldDef, CollectionArrayFieldDef, StageDef, TextSpaceDef, ImageSpaceDef, NumberSpaceDef, RecencySpaceDef, CategoricalSpaceDef } from './types.cjs';
2
- export { CollectionSearchDef, ProjectConfig, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, StageContext } from './types.cjs';
2
+ export { AgentImageInput, AgentToolDescriptor, CollectionSearchDef, ConstraintFieldType, ConstraintOperator, ConstraintPlan, ConstraintPredicate, ConstraintTrace, ConstraintTraceItem, ConstraintTraceKind, ConstraintTraceSource, ConstraintVerification, FashionPersonalizationContext, FashionRankingPolicy, FashionSearchExplanation, FashionSearchImageInput, FashionSearchRequest, FashionSearchResponse, FindProductsRequest, FindProductsResponse, GroundedProductCandidate, ProductVariantAvailability, ProjectConfig, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, StageContext } from './types.cjs';
3
3
  export { MatchCandidate, MatchComponents, MatchResult, ResolvedMatch } from './schemas.cjs';
4
4
  import 'zod';
5
5
 
@@ -9,6 +9,8 @@ declare class IdentError extends Error {
9
9
  declare function assertIdent(name: string, kind: string): void;
10
10
  declare function assertNoIdentCollisions(names: readonly string[], kind: string): void;
11
11
 
12
+ declare function isEntityDef(value: unknown): value is EntityDef;
13
+ declare function isCollectionDef(value: unknown): value is CollectionDef;
12
14
  declare const fields: {
13
15
  readonly text: (opts?: {
14
16
  required?: boolean;
@@ -131,6 +133,47 @@ declare function collection<const TFields extends Record<string, CollectionField
131
133
  };
132
134
  declare function stage(name: string, def: Omit<StageDef, "name">): StageDef;
133
135
  declare function pipeline(...stages: StageDef[]): PipelineDef;
136
+ declare const fashionAttributes: {
137
+ readonly categories: readonly ["dresses", "tops", "bottoms", "outerwear", "ethnic", "activewear", "footwear", "bags", "jewelry", "accessories", "kids", "other"];
138
+ readonly colors: readonly ["black", "white", "ivory", "beige", "brown", "tan", "grey", "red", "pink", "purple", "blue", "navy", "green", "yellow", "orange", "multicolor"];
139
+ readonly materials: readonly ["cotton", "linen", "denim", "silk", "satin", "chiffon", "knit", "polyester", "leather", "wool", "blend", "unknown"];
140
+ readonly patterns: readonly ["solid", "floral", "striped", "checked", "embroidered", "graphic", "other"];
141
+ readonly fit: readonly ["slim", "regular", "relaxed", "oversized", "tailored", "unknown"];
142
+ readonly occasions: readonly ["everyday", "office", "party", "wedding guest", "festive", "beach", "gym", "evening"];
143
+ readonly seasons: readonly ["spring", "summer", "fall", "winter", "all-season"];
144
+ readonly formality: readonly ["casual", "smart-casual", "formal", "occasion"];
145
+ readonly modesty: readonly ["modest", "moderate", "revealing"];
146
+ readonly genders: readonly ["women", "men", "unisex", "kids"];
147
+ readonly styles: readonly ["casual", "formal", "bohemian", "minimalist", "streetwear", "romantic", "classic", "sporty"];
148
+ };
149
+ declare function fashionAttributeSchema(): Record<string, unknown>;
150
+ declare function fashionEnrichmentPreset(opts?: {
151
+ model?: string;
152
+ imageField?: string;
153
+ titleField?: string;
154
+ descriptionField?: string;
155
+ }): PipelineDef;
156
+ declare function fashionSearchPreset(opts: {
157
+ name?: string;
158
+ textModel: string;
159
+ textDim: number;
160
+ imageModel?: string;
161
+ imageDim?: number;
162
+ enableVisual?: boolean;
163
+ enrichmentModel?: string;
164
+ fields?: {
165
+ title?: string;
166
+ brand?: string;
167
+ price?: string;
168
+ variants?: string;
169
+ availability?: string;
170
+ imageUrl?: string;
171
+ category?: string;
172
+ rawTags?: string;
173
+ };
174
+ }): CollectionDef & {
175
+ name: string;
176
+ };
134
177
  declare const sources: {
135
178
  readonly shopifyFeed: (opts: {
136
179
  domain: string;
@@ -150,4 +193,4 @@ declare const sources: {
150
193
  }) => ConnectorDef;
151
194
  };
152
195
 
153
- export { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, Channels, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, IdentError, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, RecencyChannel, RecencySpaceDef, Scorers, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel, assertIdent, assertNoIdentCollisions, collection, entity, f, fields, pipeline, s, sources, stage };
196
+ export { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, Channels, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, IdentError, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, RecencyChannel, RecencySpaceDef, Scorers, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel, assertIdent, assertNoIdentCollisions, collection, entity, f, fashionAttributeSchema, fashionAttributes, fashionEnrichmentPreset, fashionSearchPreset, fields, isCollectionDef, isEntityDef, pipeline, s, sources, stage };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FtsChannel, CosineChannel, RecencyChannel, SpacesChannel, CosineScorer, TrigramScorer, PhoneticEqScorer, PhoneExactScorer, AliasHitScorer, InternalCodeExactScorer, SizeUnitGateScorer, BrandGateScorer, CollectionFieldDef, CollectionEmbeddingDef, SpaceDef, PipelineDef, ConnectorDef, TypedSearchChannel, CollectionDef, FieldDef, EmbeddingDef, PhoneticDef, ParseDef, TypedScorer, EntityDef, CollectionTextFieldDef, CollectionNumberFieldDef, CollectionBooleanFieldDef, CollectionEnumFieldDef, CollectionArrayFieldDef, StageDef, TextSpaceDef, ImageSpaceDef, NumberSpaceDef, RecencySpaceDef, CategoricalSpaceDef } from './types.js';
2
- export { CollectionSearchDef, ProjectConfig, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, StageContext } from './types.js';
2
+ export { AgentImageInput, AgentToolDescriptor, CollectionSearchDef, ConstraintFieldType, ConstraintOperator, ConstraintPlan, ConstraintPredicate, ConstraintTrace, ConstraintTraceItem, ConstraintTraceKind, ConstraintTraceSource, ConstraintVerification, FashionPersonalizationContext, FashionRankingPolicy, FashionSearchExplanation, FashionSearchImageInput, FashionSearchRequest, FashionSearchResponse, FindProductsRequest, FindProductsResponse, GroundedProductCandidate, ProductVariantAvailability, ProjectConfig, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, StageContext } from './types.js';
3
3
  export { MatchCandidate, MatchComponents, MatchResult, ResolvedMatch } from './schemas.js';
4
4
  import 'zod';
5
5
 
@@ -9,6 +9,8 @@ declare class IdentError extends Error {
9
9
  declare function assertIdent(name: string, kind: string): void;
10
10
  declare function assertNoIdentCollisions(names: readonly string[], kind: string): void;
11
11
 
12
+ declare function isEntityDef(value: unknown): value is EntityDef;
13
+ declare function isCollectionDef(value: unknown): value is CollectionDef;
12
14
  declare const fields: {
13
15
  readonly text: (opts?: {
14
16
  required?: boolean;
@@ -131,6 +133,47 @@ declare function collection<const TFields extends Record<string, CollectionField
131
133
  };
132
134
  declare function stage(name: string, def: Omit<StageDef, "name">): StageDef;
133
135
  declare function pipeline(...stages: StageDef[]): PipelineDef;
136
+ declare const fashionAttributes: {
137
+ readonly categories: readonly ["dresses", "tops", "bottoms", "outerwear", "ethnic", "activewear", "footwear", "bags", "jewelry", "accessories", "kids", "other"];
138
+ readonly colors: readonly ["black", "white", "ivory", "beige", "brown", "tan", "grey", "red", "pink", "purple", "blue", "navy", "green", "yellow", "orange", "multicolor"];
139
+ readonly materials: readonly ["cotton", "linen", "denim", "silk", "satin", "chiffon", "knit", "polyester", "leather", "wool", "blend", "unknown"];
140
+ readonly patterns: readonly ["solid", "floral", "striped", "checked", "embroidered", "graphic", "other"];
141
+ readonly fit: readonly ["slim", "regular", "relaxed", "oversized", "tailored", "unknown"];
142
+ readonly occasions: readonly ["everyday", "office", "party", "wedding guest", "festive", "beach", "gym", "evening"];
143
+ readonly seasons: readonly ["spring", "summer", "fall", "winter", "all-season"];
144
+ readonly formality: readonly ["casual", "smart-casual", "formal", "occasion"];
145
+ readonly modesty: readonly ["modest", "moderate", "revealing"];
146
+ readonly genders: readonly ["women", "men", "unisex", "kids"];
147
+ readonly styles: readonly ["casual", "formal", "bohemian", "minimalist", "streetwear", "romantic", "classic", "sporty"];
148
+ };
149
+ declare function fashionAttributeSchema(): Record<string, unknown>;
150
+ declare function fashionEnrichmentPreset(opts?: {
151
+ model?: string;
152
+ imageField?: string;
153
+ titleField?: string;
154
+ descriptionField?: string;
155
+ }): PipelineDef;
156
+ declare function fashionSearchPreset(opts: {
157
+ name?: string;
158
+ textModel: string;
159
+ textDim: number;
160
+ imageModel?: string;
161
+ imageDim?: number;
162
+ enableVisual?: boolean;
163
+ enrichmentModel?: string;
164
+ fields?: {
165
+ title?: string;
166
+ brand?: string;
167
+ price?: string;
168
+ variants?: string;
169
+ availability?: string;
170
+ imageUrl?: string;
171
+ category?: string;
172
+ rawTags?: string;
173
+ };
174
+ }): CollectionDef & {
175
+ name: string;
176
+ };
134
177
  declare const sources: {
135
178
  readonly shopifyFeed: (opts: {
136
179
  domain: string;
@@ -150,4 +193,4 @@ declare const sources: {
150
193
  }) => ConnectorDef;
151
194
  };
152
195
 
153
- export { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, Channels, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, IdentError, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, RecencyChannel, RecencySpaceDef, Scorers, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel, assertIdent, assertNoIdentCollisions, collection, entity, f, fields, pipeline, s, sources, stage };
196
+ export { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, Channels, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, IdentError, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, RecencyChannel, RecencySpaceDef, Scorers, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel, assertIdent, assertNoIdentCollisions, collection, entity, f, fashionAttributeSchema, fashionAttributes, fashionEnrichmentPreset, fashionSearchPreset, fields, isCollectionDef, isEntityDef, pipeline, s, sources, stage };
package/dist/index.js CHANGED
@@ -36,6 +36,22 @@ function assertNoIdentCollisions(names, kind) {
36
36
  }
37
37
 
38
38
  // src/index.ts
39
+ var DEF_KIND = /* @__PURE__ */ Symbol.for("@samesake/core.defKind");
40
+ function brandDef(def, kind) {
41
+ Object.defineProperty(def, DEF_KIND, {
42
+ value: kind,
43
+ enumerable: false,
44
+ configurable: false,
45
+ writable: false
46
+ });
47
+ return def;
48
+ }
49
+ function isEntityDef(value) {
50
+ return !!value && typeof value === "object" && value[DEF_KIND] === "entity";
51
+ }
52
+ function isCollectionDef(value) {
53
+ return !!value && typeof value === "object" && value[DEF_KIND] === "collection";
54
+ }
39
55
  var fields = {
40
56
  text(opts = {}) {
41
57
  return { type: "text", ...opts };
@@ -74,7 +90,7 @@ function entity(name, def) {
74
90
  assertIdent(name, "entity");
75
91
  for (const s2 of def.scopes ?? []) assertIdent(s2, "scope");
76
92
  assertNoIdentCollisions(Object.keys(def.fields ?? {}), "field");
77
- return { ...def, name };
93
+ return brandDef({ ...def, name }, "entity");
78
94
  }
79
95
  var f = {
80
96
  text(opts = {}) {
@@ -150,7 +166,7 @@ function collection(name, def) {
150
166
  if (def.spaces && Object.keys(def.spaces).length > 0) {
151
167
  validateSpaceDims(def.spaces);
152
168
  }
153
- return { ...def, name };
169
+ return brandDef({ ...def, name }, "collection");
154
170
  }
155
171
  function stage(name, def) {
156
172
  return { name, ...def };
@@ -158,6 +174,176 @@ function stage(name, def) {
158
174
  function pipeline(...stages) {
159
175
  return { stages };
160
176
  }
177
+ var fashionAttributes = {
178
+ categories: [
179
+ "dresses",
180
+ "tops",
181
+ "bottoms",
182
+ "outerwear",
183
+ "ethnic",
184
+ "activewear",
185
+ "footwear",
186
+ "bags",
187
+ "jewelry",
188
+ "accessories",
189
+ "kids",
190
+ "other"
191
+ ],
192
+ colors: [
193
+ "black",
194
+ "white",
195
+ "ivory",
196
+ "beige",
197
+ "brown",
198
+ "tan",
199
+ "grey",
200
+ "red",
201
+ "pink",
202
+ "purple",
203
+ "blue",
204
+ "navy",
205
+ "green",
206
+ "yellow",
207
+ "orange",
208
+ "multicolor"
209
+ ],
210
+ materials: [
211
+ "cotton",
212
+ "linen",
213
+ "denim",
214
+ "silk",
215
+ "satin",
216
+ "chiffon",
217
+ "knit",
218
+ "polyester",
219
+ "leather",
220
+ "wool",
221
+ "blend",
222
+ "unknown"
223
+ ],
224
+ patterns: ["solid", "floral", "striped", "checked", "embroidered", "graphic", "other"],
225
+ fit: ["slim", "regular", "relaxed", "oversized", "tailored", "unknown"],
226
+ occasions: ["everyday", "office", "party", "wedding guest", "festive", "beach", "gym", "evening"],
227
+ seasons: ["spring", "summer", "fall", "winter", "all-season"],
228
+ formality: ["casual", "smart-casual", "formal", "occasion"],
229
+ modesty: ["modest", "moderate", "revealing"],
230
+ genders: ["women", "men", "unisex", "kids"],
231
+ styles: ["casual", "formal", "bohemian", "minimalist", "streetwear", "romantic", "classic", "sporty"]
232
+ };
233
+ function fashionAttributeSchema() {
234
+ return {
235
+ type: "OBJECT",
236
+ properties: {
237
+ category: { type: "STRING", enum: fashionAttributes.categories },
238
+ silhouette: { type: "STRING" },
239
+ colors: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.colors } },
240
+ material: { type: "STRING", enum: fashionAttributes.materials },
241
+ pattern: { type: "STRING", enum: fashionAttributes.patterns },
242
+ fit: { type: "STRING", enum: fashionAttributes.fit },
243
+ sleeve_length: { type: "STRING" },
244
+ neckline: { type: "STRING" },
245
+ length: { type: "STRING" },
246
+ occasions: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.occasions } },
247
+ season: { type: "STRING", enum: fashionAttributes.seasons },
248
+ formality: { type: "STRING", enum: fashionAttributes.formality },
249
+ modesty: { type: "STRING", enum: fashionAttributes.modesty },
250
+ gender: { type: "STRING", enum: fashionAttributes.genders },
251
+ style_archetypes: { type: "ARRAY", items: { type: "STRING", enum: fashionAttributes.styles } },
252
+ search_document: { type: "STRING" },
253
+ confidence: { type: "NUMBER" },
254
+ uncertain_fields: { type: "ARRAY", items: { type: "STRING" } }
255
+ },
256
+ required: ["category", "colors", "search_document", "confidence"]
257
+ };
258
+ }
259
+ function fashionEnrichmentPreset(opts = {}) {
260
+ const imageField = opts.imageField ?? "image_url";
261
+ const titleField = opts.titleField ?? "title";
262
+ const descriptionField = opts.descriptionField ?? "description";
263
+ return pipeline(
264
+ stage("fashion_attributes", {
265
+ model: opts.model,
266
+ images: (ctx) => {
267
+ const url = ctx.data[imageField];
268
+ return typeof url === "string" && url ? [url] : [];
269
+ },
270
+ prompt: (ctx) => [
271
+ "Extract structured fashion catalog attributes for visual search.",
272
+ `Title: ${String(ctx.data[titleField] ?? "")}`,
273
+ `Description: ${String(ctx.data[descriptionField] ?? "").slice(0, 1200)}`,
274
+ "Prefer observable image evidence, use allowed enum values, and write a concise shopper-facing search_document."
275
+ ].join("\n"),
276
+ schema: () => fashionAttributeSchema()
277
+ })
278
+ );
279
+ }
280
+ function fashionSearchPreset(opts) {
281
+ const fieldsMap = {
282
+ title: opts.fields?.title ?? "title",
283
+ brand: opts.fields?.brand ?? "brand",
284
+ price: opts.fields?.price ?? "price",
285
+ availability: opts.fields?.availability ?? "available",
286
+ imageUrl: opts.fields?.imageUrl ?? "image_url",
287
+ category: opts.fields?.category ?? "category"
288
+ };
289
+ const spaces = {
290
+ intent: s.text({
291
+ source: "$enriched.search_document $title",
292
+ model: opts.textModel,
293
+ dim: opts.textDim,
294
+ taskType: "RETRIEVAL_DOCUMENT"
295
+ }),
296
+ price: s.number({ field: "price", mode: "closer", dims: 8, min: 0, max: 1e5, scale: "log" })
297
+ };
298
+ if (opts.enableVisual !== false && opts.imageModel && opts.imageDim) {
299
+ spaces.visual = s.image({
300
+ source: `$${fieldsMap.imageUrl}`,
301
+ model: opts.imageModel,
302
+ dim: opts.imageDim,
303
+ taskType: "RETRIEVAL_DOCUMENT"
304
+ });
305
+ }
306
+ return collection(opts.name ?? "products", {
307
+ fields: {
308
+ title: f.text({ searchable: true, path: fieldsMap.title }),
309
+ brand: f.text({ filterable: true, facet: true, path: fieldsMap.brand }),
310
+ price: f.number({ filterable: true, facet: "range", budget: true, path: fieldsMap.price }),
311
+ available: f.boolean({ filterable: true, facet: true, path: fieldsMap.availability }),
312
+ category: f.text({ filterable: true, facet: true, path: fieldsMap.category }),
313
+ colors: f.array(f.enum(fashionAttributes.colors), { filterable: true, soft: true, path: "enriched.colors" }),
314
+ material: f.enum(fashionAttributes.materials, { filterable: true, soft: true, path: "enriched.material" }),
315
+ fit: f.enum(fashionAttributes.fit, { filterable: true, soft: true, path: "enriched.fit" }),
316
+ styles: f.array(f.enum(fashionAttributes.styles), { filterable: true, soft: true, path: "enriched.style_archetypes" })
317
+ },
318
+ enrich: fashionEnrichmentPreset({ model: opts.enrichmentModel, imageField: fieldsMap.imageUrl, titleField: fieldsMap.title }),
319
+ embeddings: {
320
+ intent: {
321
+ source: "$enriched.search_document $title",
322
+ model: opts.textModel,
323
+ dim: opts.textDim,
324
+ taskType: "RETRIEVAL_DOCUMENT"
325
+ }
326
+ },
327
+ spaces,
328
+ search: {
329
+ channels: [
330
+ Channels.fts({ fields: ["title"], weight: 1 }),
331
+ Channels.cosine({ embedding: "intent", weight: 1 }),
332
+ Channels.spaces({ weight: 1 })
333
+ ],
334
+ combiner: "rrf",
335
+ defaultSpaceWeights: {
336
+ intent: 1,
337
+ price: 0.4,
338
+ ...spaces.visual ? { visual: 1.2 } : {}
339
+ },
340
+ nlq: {
341
+ semanticRewrite: true,
342
+ schema: fashionAttributeSchema()
343
+ }
344
+ }
345
+ });
346
+ }
161
347
  var sources = {
162
348
  shopifyFeed(opts) {
163
349
  return {
@@ -198,7 +384,13 @@ export {
198
384
  collection,
199
385
  entity,
200
386
  f,
387
+ fashionAttributeSchema,
388
+ fashionAttributes,
389
+ fashionEnrichmentPreset,
390
+ fashionSearchPreset,
201
391
  fields,
392
+ isCollectionDef,
393
+ isEntityDef,
202
394
  pipeline,
203
395
  s,
204
396
  sources,
package/dist/types.d.cts CHANGED
@@ -308,9 +308,194 @@ type SearchWeightsInput<S extends string = string> = {
308
308
  recency?: number;
309
309
  spaces?: number | Partial<Record<S, number>>;
310
310
  };
311
+ type ConstraintTraceSource = "nlq" | "explicit" | "budget_hint" | "agent";
312
+ type ConstraintFieldType = "text" | "number" | "boolean" | "enum" | "array";
313
+ type ConstraintOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "exclude" | "not";
314
+ type ConstraintTraceKind = "eq" | "not_eq" | "min" | "max" | "range" | "in" | "not_in" | "contains" | "exclude" | "boolean";
315
+ interface ConstraintTraceItem {
316
+ field: string;
317
+ source: ConstraintTraceSource;
318
+ kind: ConstraintTraceKind;
319
+ operator?: string;
320
+ value?: unknown;
321
+ soft?: boolean;
322
+ }
323
+ interface ConstraintPredicate {
324
+ field: string;
325
+ fieldType: ConstraintFieldType;
326
+ operator: ConstraintOperator;
327
+ value: unknown;
328
+ source?: ConstraintTraceSource;
329
+ soft?: boolean;
330
+ }
331
+ interface ConstraintPlan {
332
+ predicates: ConstraintPredicate[];
333
+ excludedTerms: string[];
334
+ relaxedFields: string[];
335
+ }
336
+ interface ConstraintTrace {
337
+ semanticQuery?: string;
338
+ items: ConstraintTraceItem[];
339
+ plan: ConstraintPlan;
340
+ derivedFilters: Record<string, unknown>;
341
+ explicitFilters: Record<string, unknown>;
342
+ appliedFilters: Record<string, unknown>;
343
+ relaxedFields: string[];
344
+ excludedTerms: string[];
345
+ budgetHints: Record<string, "cheap" | "premium">;
346
+ }
347
+ interface FashionSearchImageInput {
348
+ /** Remote image URL. The server fetches it through the same hardened image guard used for indexing. */
349
+ url?: string;
350
+ /** Base64 image bytes for callers that do not want Samesake to fetch a URL. */
351
+ bytesBase64?: string;
352
+ /** In-process callers may pass bytes directly. Not valid over JSON HTTP. */
353
+ bytes?: Uint8Array;
354
+ mimeType?: string;
355
+ /** Product id whose catalog image should be used as the query image. */
356
+ productId?: string;
357
+ }
358
+ interface FashionRankingPolicy {
359
+ weights?: {
360
+ relevance?: number;
361
+ visual?: number;
362
+ availability?: number;
363
+ newness?: number;
364
+ business?: number;
365
+ personalization?: number;
366
+ };
367
+ /** Numeric field used as a merchant/business signal, for example margin or sell-through. */
368
+ businessField?: string;
369
+ boostAvailable?: boolean;
370
+ buryUnavailable?: boolean;
371
+ }
372
+ interface FashionPersonalizationContext {
373
+ size?: string;
374
+ priceBand?: {
375
+ min?: number;
376
+ max?: number;
377
+ };
378
+ preferredBrands?: string[];
379
+ blockedBrands?: string[];
380
+ viewedProductIds?: string[];
381
+ avoidedStyles?: string[];
382
+ colorAffinity?: Record<string, number>;
383
+ }
384
+ interface FashionSearchRequest<S extends string = string> {
385
+ q?: string;
386
+ image?: FashionSearchImageInput;
387
+ filters?: Record<string, unknown>;
388
+ weights?: SearchWeightsInput<S>;
389
+ rankingPolicy?: FashionRankingPolicy;
390
+ personalization?: FashionPersonalizationContext;
391
+ limit?: number;
392
+ offset?: number;
393
+ debug?: boolean;
394
+ explain?: boolean;
395
+ recoverNoResults?: boolean;
396
+ }
397
+ interface FashionSearchExplanation {
398
+ hitId: string;
399
+ factors: Record<string, number | boolean | string | null>;
400
+ appliedFilters: string[];
401
+ }
402
+ interface FashionSearchResponse {
403
+ hits: Array<Record<string, unknown> & {
404
+ id: string;
405
+ score: number;
406
+ }>;
407
+ parsed?: Record<string, unknown>;
408
+ appliedFilters: Record<string, unknown>;
409
+ constraintTrace?: ConstraintTrace;
410
+ explanations?: FashionSearchExplanation[];
411
+ fallback?: {
412
+ reason: "no_results" | "low_confidence";
413
+ relaxedFilters: string[];
414
+ };
415
+ debug?: Record<string, unknown>;
416
+ took_ms: number;
417
+ }
418
+ type AgentImageInput = {
419
+ kind: "url";
420
+ url: string;
421
+ } | {
422
+ kind: "bytes";
423
+ bytesBase64: string;
424
+ mimeType?: string;
425
+ } | {
426
+ kind: "product_image";
427
+ productId: string;
428
+ imageField?: string;
429
+ };
430
+ interface FindProductsRequest {
431
+ intent?: string;
432
+ image?: AgentImageInput;
433
+ constraints?: Record<string, unknown>;
434
+ shopperContext?: Record<string, unknown>;
435
+ constraintMode?: "best_effort" | "strict";
436
+ explain?: boolean;
437
+ limit?: number;
438
+ }
439
+ interface ProductVariantAvailability {
440
+ id?: string;
441
+ title?: string;
442
+ size?: string;
443
+ price?: number;
444
+ available?: boolean;
445
+ inventoryQuantity?: number;
446
+ updatedAt?: string;
447
+ }
448
+ interface ConstraintVerification {
449
+ status: "satisfied" | "violated" | "unknown";
450
+ satisfied: string[];
451
+ violated: string[];
452
+ unknown: string[];
453
+ strictExcluded?: boolean;
454
+ }
455
+ interface GroundedProductCandidate {
456
+ id: string;
457
+ title?: string;
458
+ url?: string;
459
+ imageUrl?: string;
460
+ price?: {
461
+ amount: number;
462
+ currency?: string;
463
+ lastUpdatedAt?: string;
464
+ };
465
+ availability?: {
466
+ inStock?: boolean;
467
+ variants?: ProductVariantAvailability[];
468
+ lastCheckedAt?: string;
469
+ freshness: "fresh" | "stale" | "unknown";
470
+ };
471
+ score: number;
472
+ data: Record<string, unknown>;
473
+ grounding: {
474
+ project: string;
475
+ collection: string;
476
+ productId: string;
477
+ indexedAt?: string;
478
+ sourceUpdatedAt?: string;
479
+ };
480
+ verification: ConstraintVerification;
481
+ why?: Record<string, unknown>;
482
+ }
483
+ interface FindProductsResponse {
484
+ products: GroundedProductCandidate[];
485
+ parsed?: Record<string, unknown>;
486
+ constraintTrace?: ConstraintTrace;
487
+ relaxed?: boolean;
488
+ took_ms: number;
489
+ }
490
+ interface AgentToolDescriptor {
491
+ name: "find_products" | "find_similar_products" | "compare_products" | "explain_result" | "get_product_availability" | "recover_no_results";
492
+ description: string;
493
+ inputSchema: Record<string, unknown>;
494
+ outputSchema: Record<string, unknown>;
495
+ }
311
496
  interface ProjectConfig {
312
497
  entities?: EntityDef[];
313
498
  collections?: CollectionDef[];
314
499
  }
315
500
 
316
- export type { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionSearchDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, ProjectConfig, RecencyChannel, RecencySpaceDef, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageContext, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel };
501
+ export type { AgentImageInput, AgentToolDescriptor, AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionSearchDef, CollectionTextFieldDef, ConnectorDef, ConstraintFieldType, ConstraintOperator, ConstraintPlan, ConstraintPredicate, ConstraintTrace, ConstraintTraceItem, ConstraintTraceKind, ConstraintTraceSource, ConstraintVerification, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FashionPersonalizationContext, FashionRankingPolicy, FashionSearchExplanation, FashionSearchImageInput, FashionSearchRequest, FashionSearchResponse, FieldDef, FindProductsRequest, FindProductsResponse, FtsChannel, GroundedProductCandidate, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, ProductVariantAvailability, ProjectConfig, RecencyChannel, RecencySpaceDef, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageContext, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel };
package/dist/types.d.ts CHANGED
@@ -308,9 +308,194 @@ type SearchWeightsInput<S extends string = string> = {
308
308
  recency?: number;
309
309
  spaces?: number | Partial<Record<S, number>>;
310
310
  };
311
+ type ConstraintTraceSource = "nlq" | "explicit" | "budget_hint" | "agent";
312
+ type ConstraintFieldType = "text" | "number" | "boolean" | "enum" | "array";
313
+ type ConstraintOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "exclude" | "not";
314
+ type ConstraintTraceKind = "eq" | "not_eq" | "min" | "max" | "range" | "in" | "not_in" | "contains" | "exclude" | "boolean";
315
+ interface ConstraintTraceItem {
316
+ field: string;
317
+ source: ConstraintTraceSource;
318
+ kind: ConstraintTraceKind;
319
+ operator?: string;
320
+ value?: unknown;
321
+ soft?: boolean;
322
+ }
323
+ interface ConstraintPredicate {
324
+ field: string;
325
+ fieldType: ConstraintFieldType;
326
+ operator: ConstraintOperator;
327
+ value: unknown;
328
+ source?: ConstraintTraceSource;
329
+ soft?: boolean;
330
+ }
331
+ interface ConstraintPlan {
332
+ predicates: ConstraintPredicate[];
333
+ excludedTerms: string[];
334
+ relaxedFields: string[];
335
+ }
336
+ interface ConstraintTrace {
337
+ semanticQuery?: string;
338
+ items: ConstraintTraceItem[];
339
+ plan: ConstraintPlan;
340
+ derivedFilters: Record<string, unknown>;
341
+ explicitFilters: Record<string, unknown>;
342
+ appliedFilters: Record<string, unknown>;
343
+ relaxedFields: string[];
344
+ excludedTerms: string[];
345
+ budgetHints: Record<string, "cheap" | "premium">;
346
+ }
347
+ interface FashionSearchImageInput {
348
+ /** Remote image URL. The server fetches it through the same hardened image guard used for indexing. */
349
+ url?: string;
350
+ /** Base64 image bytes for callers that do not want Samesake to fetch a URL. */
351
+ bytesBase64?: string;
352
+ /** In-process callers may pass bytes directly. Not valid over JSON HTTP. */
353
+ bytes?: Uint8Array;
354
+ mimeType?: string;
355
+ /** Product id whose catalog image should be used as the query image. */
356
+ productId?: string;
357
+ }
358
+ interface FashionRankingPolicy {
359
+ weights?: {
360
+ relevance?: number;
361
+ visual?: number;
362
+ availability?: number;
363
+ newness?: number;
364
+ business?: number;
365
+ personalization?: number;
366
+ };
367
+ /** Numeric field used as a merchant/business signal, for example margin or sell-through. */
368
+ businessField?: string;
369
+ boostAvailable?: boolean;
370
+ buryUnavailable?: boolean;
371
+ }
372
+ interface FashionPersonalizationContext {
373
+ size?: string;
374
+ priceBand?: {
375
+ min?: number;
376
+ max?: number;
377
+ };
378
+ preferredBrands?: string[];
379
+ blockedBrands?: string[];
380
+ viewedProductIds?: string[];
381
+ avoidedStyles?: string[];
382
+ colorAffinity?: Record<string, number>;
383
+ }
384
+ interface FashionSearchRequest<S extends string = string> {
385
+ q?: string;
386
+ image?: FashionSearchImageInput;
387
+ filters?: Record<string, unknown>;
388
+ weights?: SearchWeightsInput<S>;
389
+ rankingPolicy?: FashionRankingPolicy;
390
+ personalization?: FashionPersonalizationContext;
391
+ limit?: number;
392
+ offset?: number;
393
+ debug?: boolean;
394
+ explain?: boolean;
395
+ recoverNoResults?: boolean;
396
+ }
397
+ interface FashionSearchExplanation {
398
+ hitId: string;
399
+ factors: Record<string, number | boolean | string | null>;
400
+ appliedFilters: string[];
401
+ }
402
+ interface FashionSearchResponse {
403
+ hits: Array<Record<string, unknown> & {
404
+ id: string;
405
+ score: number;
406
+ }>;
407
+ parsed?: Record<string, unknown>;
408
+ appliedFilters: Record<string, unknown>;
409
+ constraintTrace?: ConstraintTrace;
410
+ explanations?: FashionSearchExplanation[];
411
+ fallback?: {
412
+ reason: "no_results" | "low_confidence";
413
+ relaxedFilters: string[];
414
+ };
415
+ debug?: Record<string, unknown>;
416
+ took_ms: number;
417
+ }
418
+ type AgentImageInput = {
419
+ kind: "url";
420
+ url: string;
421
+ } | {
422
+ kind: "bytes";
423
+ bytesBase64: string;
424
+ mimeType?: string;
425
+ } | {
426
+ kind: "product_image";
427
+ productId: string;
428
+ imageField?: string;
429
+ };
430
+ interface FindProductsRequest {
431
+ intent?: string;
432
+ image?: AgentImageInput;
433
+ constraints?: Record<string, unknown>;
434
+ shopperContext?: Record<string, unknown>;
435
+ constraintMode?: "best_effort" | "strict";
436
+ explain?: boolean;
437
+ limit?: number;
438
+ }
439
+ interface ProductVariantAvailability {
440
+ id?: string;
441
+ title?: string;
442
+ size?: string;
443
+ price?: number;
444
+ available?: boolean;
445
+ inventoryQuantity?: number;
446
+ updatedAt?: string;
447
+ }
448
+ interface ConstraintVerification {
449
+ status: "satisfied" | "violated" | "unknown";
450
+ satisfied: string[];
451
+ violated: string[];
452
+ unknown: string[];
453
+ strictExcluded?: boolean;
454
+ }
455
+ interface GroundedProductCandidate {
456
+ id: string;
457
+ title?: string;
458
+ url?: string;
459
+ imageUrl?: string;
460
+ price?: {
461
+ amount: number;
462
+ currency?: string;
463
+ lastUpdatedAt?: string;
464
+ };
465
+ availability?: {
466
+ inStock?: boolean;
467
+ variants?: ProductVariantAvailability[];
468
+ lastCheckedAt?: string;
469
+ freshness: "fresh" | "stale" | "unknown";
470
+ };
471
+ score: number;
472
+ data: Record<string, unknown>;
473
+ grounding: {
474
+ project: string;
475
+ collection: string;
476
+ productId: string;
477
+ indexedAt?: string;
478
+ sourceUpdatedAt?: string;
479
+ };
480
+ verification: ConstraintVerification;
481
+ why?: Record<string, unknown>;
482
+ }
483
+ interface FindProductsResponse {
484
+ products: GroundedProductCandidate[];
485
+ parsed?: Record<string, unknown>;
486
+ constraintTrace?: ConstraintTrace;
487
+ relaxed?: boolean;
488
+ took_ms: number;
489
+ }
490
+ interface AgentToolDescriptor {
491
+ name: "find_products" | "find_similar_products" | "compare_products" | "explain_result" | "get_product_availability" | "recover_no_results";
492
+ description: string;
493
+ inputSchema: Record<string, unknown>;
494
+ outputSchema: Record<string, unknown>;
495
+ }
311
496
  interface ProjectConfig {
312
497
  entities?: EntityDef[];
313
498
  collections?: CollectionDef[];
314
499
  }
315
500
 
316
- export type { AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionSearchDef, CollectionTextFieldDef, ConnectorDef, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FieldDef, FtsChannel, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, ProjectConfig, RecencyChannel, RecencySpaceDef, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageContext, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel };
501
+ export type { AgentImageInput, AgentToolDescriptor, AliasHitScorer, BrandGateScorer, CategoricalSpaceDef, CollectionArrayFieldDef, CollectionBooleanFieldDef, CollectionDef, CollectionEmbeddingDef, CollectionEnumFieldDef, CollectionFieldDef, CollectionNumberFieldDef, CollectionSearchDef, CollectionTextFieldDef, ConnectorDef, ConstraintFieldType, ConstraintOperator, ConstraintPlan, ConstraintPredicate, ConstraintTrace, ConstraintTraceItem, ConstraintTraceKind, ConstraintTraceSource, ConstraintVerification, CosineChannel, CosineScorer, EmbeddingDef, EntityDef, FashionPersonalizationContext, FashionRankingPolicy, FashionSearchExplanation, FashionSearchImageInput, FashionSearchRequest, FashionSearchResponse, FieldDef, FindProductsRequest, FindProductsResponse, FtsChannel, GroundedProductCandidate, ImageSpaceDef, InternalCodeExactScorer, NumberSpaceDef, ParseDef, PhoneExactScorer, PhoneticDef, PhoneticEqScorer, PipelineDef, ProductVariantAvailability, ProjectConfig, RecencyChannel, RecencySpaceDef, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageContext, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samesake/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/asyncdotengineering/samesake"
@@ -43,7 +43,11 @@
43
43
  }
44
44
  }
45
45
  },
46
- "files": ["dist", "README.md", "LICENSE"],
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
47
51
  "publishConfig": {
48
52
  "access": "public"
49
53
  },