@samesake/core 1.0.0 → 1.2.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
- 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';
1
+ import { FtsChannel, CosineChannel, RecencyChannel, SpacesChannel, CosineScorer, TrigramScorer, PhoneticEqScorer, PhoneExactScorer, AliasHitScorer, InternalCodeExactScorer, SizeUnitGateScorer, BrandGateScorer, CollectionFieldDef, CollectionEmbeddingDef, SpaceDef, PipelineDef, ConnectorDef, TypedSearchChannel, SchemaInput, CollectionDef, FieldDef, EmbeddingDef, PhoneticDef, ParseDef, TypedScorer, EntityDef, CollectionTextFieldDef, CollectionNumberFieldDef, CollectionBooleanFieldDef, CollectionEnumFieldDef, CollectionArrayFieldDef, StageDef, TextSpaceDef, ImageSpaceDef, NumberSpaceDef, RecencySpaceDef, CategoricalSpaceDef } 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;
@@ -121,7 +123,7 @@ type CollectionInput<TFields extends Record<string, CollectionFieldDef>, TEmbedd
121
123
  instructions?: string;
122
124
  semanticRewrite?: boolean;
123
125
  enable?: boolean;
124
- schema?: Record<string, unknown>;
126
+ schema?: SchemaInput;
125
127
  model?: string;
126
128
  };
127
129
  };
@@ -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, SchemaInput, 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
- 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';
1
+ import { FtsChannel, CosineChannel, RecencyChannel, SpacesChannel, CosineScorer, TrigramScorer, PhoneticEqScorer, PhoneExactScorer, AliasHitScorer, InternalCodeExactScorer, SizeUnitGateScorer, BrandGateScorer, CollectionFieldDef, CollectionEmbeddingDef, SpaceDef, PipelineDef, ConnectorDef, TypedSearchChannel, SchemaInput, CollectionDef, FieldDef, EmbeddingDef, PhoneticDef, ParseDef, TypedScorer, EntityDef, CollectionTextFieldDef, CollectionNumberFieldDef, CollectionBooleanFieldDef, CollectionEnumFieldDef, CollectionArrayFieldDef, StageDef, TextSpaceDef, ImageSpaceDef, NumberSpaceDef, RecencySpaceDef, CategoricalSpaceDef } 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;
@@ -121,7 +123,7 @@ type CollectionInput<TFields extends Record<string, CollectionFieldDef>, TEmbedd
121
123
  instructions?: string;
122
124
  semanticRewrite?: boolean;
123
125
  enable?: boolean;
124
- schema?: Record<string, unknown>;
126
+ schema?: SchemaInput;
125
127
  model?: string;
126
128
  };
127
129
  };
@@ -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, SchemaInput, 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
@@ -1,5 +1,5 @@
1
+ import { ZodType } from 'zod';
1
2
  export { MatchCandidate, MatchComponents, MatchResult, ResolvedMatch } from './schemas.cjs';
2
- import 'zod';
3
3
 
4
4
  interface FieldDef {
5
5
  type: "text" | "number";
@@ -269,7 +269,7 @@ interface CollectionSearchDef {
269
269
  instructions?: string;
270
270
  semanticRewrite?: boolean;
271
271
  enable?: boolean;
272
- schema?: Record<string, unknown>;
272
+ schema?: SchemaInput;
273
273
  model?: string;
274
274
  };
275
275
  }
@@ -277,13 +277,14 @@ interface StageContext {
277
277
  data: Record<string, unknown>;
278
278
  enriched: Record<string, unknown>;
279
279
  }
280
+ type SchemaInput = ZodType | Record<string, unknown>;
280
281
  interface StageDef {
281
282
  name: string;
282
283
  model?: string;
283
284
  condition?: (ctx: StageContext) => boolean;
284
285
  prompt: (ctx: StageContext) => string;
285
286
  images?: (ctx: StageContext) => string[];
286
- schema: (ctx: StageContext) => Record<string, unknown>;
287
+ schema: (ctx: StageContext) => SchemaInput;
287
288
  }
288
289
  interface PipelineDef {
289
290
  stages: StageDef[];
@@ -308,9 +309,194 @@ type SearchWeightsInput<S extends string = string> = {
308
309
  recency?: number;
309
310
  spaces?: number | Partial<Record<S, number>>;
310
311
  };
312
+ type ConstraintTraceSource = "nlq" | "explicit" | "budget_hint" | "agent";
313
+ type ConstraintFieldType = "text" | "number" | "boolean" | "enum" | "array";
314
+ type ConstraintOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "exclude" | "not";
315
+ type ConstraintTraceKind = "eq" | "not_eq" | "min" | "max" | "range" | "in" | "not_in" | "contains" | "exclude" | "boolean";
316
+ interface ConstraintTraceItem {
317
+ field: string;
318
+ source: ConstraintTraceSource;
319
+ kind: ConstraintTraceKind;
320
+ operator?: string;
321
+ value?: unknown;
322
+ soft?: boolean;
323
+ }
324
+ interface ConstraintPredicate {
325
+ field: string;
326
+ fieldType: ConstraintFieldType;
327
+ operator: ConstraintOperator;
328
+ value: unknown;
329
+ source?: ConstraintTraceSource;
330
+ soft?: boolean;
331
+ }
332
+ interface ConstraintPlan {
333
+ predicates: ConstraintPredicate[];
334
+ excludedTerms: string[];
335
+ relaxedFields: string[];
336
+ }
337
+ interface ConstraintTrace {
338
+ semanticQuery?: string;
339
+ items: ConstraintTraceItem[];
340
+ plan: ConstraintPlan;
341
+ derivedFilters: Record<string, unknown>;
342
+ explicitFilters: Record<string, unknown>;
343
+ appliedFilters: Record<string, unknown>;
344
+ relaxedFields: string[];
345
+ excludedTerms: string[];
346
+ budgetHints: Record<string, "cheap" | "premium">;
347
+ }
348
+ interface FashionSearchImageInput {
349
+ /** Remote image URL. The server fetches it through the same hardened image guard used for indexing. */
350
+ url?: string;
351
+ /** Base64 image bytes for callers that do not want Samesake to fetch a URL. */
352
+ bytesBase64?: string;
353
+ /** In-process callers may pass bytes directly. Not valid over JSON HTTP. */
354
+ bytes?: Uint8Array;
355
+ mimeType?: string;
356
+ /** Product id whose catalog image should be used as the query image. */
357
+ productId?: string;
358
+ }
359
+ interface FashionRankingPolicy {
360
+ weights?: {
361
+ relevance?: number;
362
+ visual?: number;
363
+ availability?: number;
364
+ newness?: number;
365
+ business?: number;
366
+ personalization?: number;
367
+ };
368
+ /** Numeric field used as a merchant/business signal, for example margin or sell-through. */
369
+ businessField?: string;
370
+ boostAvailable?: boolean;
371
+ buryUnavailable?: boolean;
372
+ }
373
+ interface FashionPersonalizationContext {
374
+ size?: string;
375
+ priceBand?: {
376
+ min?: number;
377
+ max?: number;
378
+ };
379
+ preferredBrands?: string[];
380
+ blockedBrands?: string[];
381
+ viewedProductIds?: string[];
382
+ avoidedStyles?: string[];
383
+ colorAffinity?: Record<string, number>;
384
+ }
385
+ interface FashionSearchRequest<S extends string = string> {
386
+ q?: string;
387
+ image?: FashionSearchImageInput;
388
+ filters?: Record<string, unknown>;
389
+ weights?: SearchWeightsInput<S>;
390
+ rankingPolicy?: FashionRankingPolicy;
391
+ personalization?: FashionPersonalizationContext;
392
+ limit?: number;
393
+ offset?: number;
394
+ debug?: boolean;
395
+ explain?: boolean;
396
+ recoverNoResults?: boolean;
397
+ }
398
+ interface FashionSearchExplanation {
399
+ hitId: string;
400
+ factors: Record<string, number | boolean | string | null>;
401
+ appliedFilters: string[];
402
+ }
403
+ interface FashionSearchResponse {
404
+ hits: Array<Record<string, unknown> & {
405
+ id: string;
406
+ score: number;
407
+ }>;
408
+ parsed?: Record<string, unknown>;
409
+ appliedFilters: Record<string, unknown>;
410
+ constraintTrace?: ConstraintTrace;
411
+ explanations?: FashionSearchExplanation[];
412
+ fallback?: {
413
+ reason: "no_results" | "low_confidence";
414
+ relaxedFilters: string[];
415
+ };
416
+ debug?: Record<string, unknown>;
417
+ took_ms: number;
418
+ }
419
+ type AgentImageInput = {
420
+ kind: "url";
421
+ url: string;
422
+ } | {
423
+ kind: "bytes";
424
+ bytesBase64: string;
425
+ mimeType?: string;
426
+ } | {
427
+ kind: "product_image";
428
+ productId: string;
429
+ imageField?: string;
430
+ };
431
+ interface FindProductsRequest {
432
+ intent?: string;
433
+ image?: AgentImageInput;
434
+ constraints?: Record<string, unknown>;
435
+ shopperContext?: Record<string, unknown>;
436
+ constraintMode?: "best_effort" | "strict";
437
+ explain?: boolean;
438
+ limit?: number;
439
+ }
440
+ interface ProductVariantAvailability {
441
+ id?: string;
442
+ title?: string;
443
+ size?: string;
444
+ price?: number;
445
+ available?: boolean;
446
+ inventoryQuantity?: number;
447
+ updatedAt?: string;
448
+ }
449
+ interface ConstraintVerification {
450
+ status: "satisfied" | "violated" | "unknown";
451
+ satisfied: string[];
452
+ violated: string[];
453
+ unknown: string[];
454
+ strictExcluded?: boolean;
455
+ }
456
+ interface GroundedProductCandidate {
457
+ id: string;
458
+ title?: string;
459
+ url?: string;
460
+ imageUrl?: string;
461
+ price?: {
462
+ amount: number;
463
+ currency?: string;
464
+ lastUpdatedAt?: string;
465
+ };
466
+ availability?: {
467
+ inStock?: boolean;
468
+ variants?: ProductVariantAvailability[];
469
+ lastCheckedAt?: string;
470
+ freshness: "fresh" | "stale" | "unknown";
471
+ };
472
+ score: number;
473
+ data: Record<string, unknown>;
474
+ grounding: {
475
+ project: string;
476
+ collection: string;
477
+ productId: string;
478
+ indexedAt?: string;
479
+ sourceUpdatedAt?: string;
480
+ };
481
+ verification: ConstraintVerification;
482
+ why?: Record<string, unknown>;
483
+ }
484
+ interface FindProductsResponse {
485
+ products: GroundedProductCandidate[];
486
+ parsed?: Record<string, unknown>;
487
+ constraintTrace?: ConstraintTrace;
488
+ relaxed?: boolean;
489
+ took_ms: number;
490
+ }
491
+ interface AgentToolDescriptor {
492
+ name: "find_products" | "find_similar_products" | "compare_products" | "explain_result" | "get_product_availability" | "recover_no_results";
493
+ description: string;
494
+ inputSchema: Record<string, unknown>;
495
+ outputSchema: Record<string, unknown>;
496
+ }
311
497
  interface ProjectConfig {
312
498
  entities?: EntityDef[];
313
499
  collections?: CollectionDef[];
314
500
  }
315
501
 
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 };
502
+ 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, SchemaInput, ScorerDef, ScoringDef, SearchChannelDef, SearchWeightsInput, SizeUnitGateScorer, SpaceDef, SpacesChannel, StageContext, StageDef, TextSpaceDef, TrigramScorer, TypedScorer, TypedSearchChannel };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { ZodType } from 'zod';
1
2
  export { MatchCandidate, MatchComponents, MatchResult, ResolvedMatch } from './schemas.js';
2
- import 'zod';
3
3
 
4
4
  interface FieldDef {
5
5
  type: "text" | "number";
@@ -269,7 +269,7 @@ interface CollectionSearchDef {
269
269
  instructions?: string;
270
270
  semanticRewrite?: boolean;
271
271
  enable?: boolean;
272
- schema?: Record<string, unknown>;
272
+ schema?: SchemaInput;
273
273
  model?: string;
274
274
  };
275
275
  }
@@ -277,13 +277,14 @@ interface StageContext {
277
277
  data: Record<string, unknown>;
278
278
  enriched: Record<string, unknown>;
279
279
  }
280
+ type SchemaInput = ZodType | Record<string, unknown>;
280
281
  interface StageDef {
281
282
  name: string;
282
283
  model?: string;
283
284
  condition?: (ctx: StageContext) => boolean;
284
285
  prompt: (ctx: StageContext) => string;
285
286
  images?: (ctx: StageContext) => string[];
286
- schema: (ctx: StageContext) => Record<string, unknown>;
287
+ schema: (ctx: StageContext) => SchemaInput;
287
288
  }
288
289
  interface PipelineDef {
289
290
  stages: StageDef[];
@@ -308,9 +309,194 @@ type SearchWeightsInput<S extends string = string> = {
308
309
  recency?: number;
309
310
  spaces?: number | Partial<Record<S, number>>;
310
311
  };
312
+ type ConstraintTraceSource = "nlq" | "explicit" | "budget_hint" | "agent";
313
+ type ConstraintFieldType = "text" | "number" | "boolean" | "enum" | "array";
314
+ type ConstraintOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" | "exclude" | "not";
315
+ type ConstraintTraceKind = "eq" | "not_eq" | "min" | "max" | "range" | "in" | "not_in" | "contains" | "exclude" | "boolean";
316
+ interface ConstraintTraceItem {
317
+ field: string;
318
+ source: ConstraintTraceSource;
319
+ kind: ConstraintTraceKind;
320
+ operator?: string;
321
+ value?: unknown;
322
+ soft?: boolean;
323
+ }
324
+ interface ConstraintPredicate {
325
+ field: string;
326
+ fieldType: ConstraintFieldType;
327
+ operator: ConstraintOperator;
328
+ value: unknown;
329
+ source?: ConstraintTraceSource;
330
+ soft?: boolean;
331
+ }
332
+ interface ConstraintPlan {
333
+ predicates: ConstraintPredicate[];
334
+ excludedTerms: string[];
335
+ relaxedFields: string[];
336
+ }
337
+ interface ConstraintTrace {
338
+ semanticQuery?: string;
339
+ items: ConstraintTraceItem[];
340
+ plan: ConstraintPlan;
341
+ derivedFilters: Record<string, unknown>;
342
+ explicitFilters: Record<string, unknown>;
343
+ appliedFilters: Record<string, unknown>;
344
+ relaxedFields: string[];
345
+ excludedTerms: string[];
346
+ budgetHints: Record<string, "cheap" | "premium">;
347
+ }
348
+ interface FashionSearchImageInput {
349
+ /** Remote image URL. The server fetches it through the same hardened image guard used for indexing. */
350
+ url?: string;
351
+ /** Base64 image bytes for callers that do not want Samesake to fetch a URL. */
352
+ bytesBase64?: string;
353
+ /** In-process callers may pass bytes directly. Not valid over JSON HTTP. */
354
+ bytes?: Uint8Array;
355
+ mimeType?: string;
356
+ /** Product id whose catalog image should be used as the query image. */
357
+ productId?: string;
358
+ }
359
+ interface FashionRankingPolicy {
360
+ weights?: {
361
+ relevance?: number;
362
+ visual?: number;
363
+ availability?: number;
364
+ newness?: number;
365
+ business?: number;
366
+ personalization?: number;
367
+ };
368
+ /** Numeric field used as a merchant/business signal, for example margin or sell-through. */
369
+ businessField?: string;
370
+ boostAvailable?: boolean;
371
+ buryUnavailable?: boolean;
372
+ }
373
+ interface FashionPersonalizationContext {
374
+ size?: string;
375
+ priceBand?: {
376
+ min?: number;
377
+ max?: number;
378
+ };
379
+ preferredBrands?: string[];
380
+ blockedBrands?: string[];
381
+ viewedProductIds?: string[];
382
+ avoidedStyles?: string[];
383
+ colorAffinity?: Record<string, number>;
384
+ }
385
+ interface FashionSearchRequest<S extends string = string> {
386
+ q?: string;
387
+ image?: FashionSearchImageInput;
388
+ filters?: Record<string, unknown>;
389
+ weights?: SearchWeightsInput<S>;
390
+ rankingPolicy?: FashionRankingPolicy;
391
+ personalization?: FashionPersonalizationContext;
392
+ limit?: number;
393
+ offset?: number;
394
+ debug?: boolean;
395
+ explain?: boolean;
396
+ recoverNoResults?: boolean;
397
+ }
398
+ interface FashionSearchExplanation {
399
+ hitId: string;
400
+ factors: Record<string, number | boolean | string | null>;
401
+ appliedFilters: string[];
402
+ }
403
+ interface FashionSearchResponse {
404
+ hits: Array<Record<string, unknown> & {
405
+ id: string;
406
+ score: number;
407
+ }>;
408
+ parsed?: Record<string, unknown>;
409
+ appliedFilters: Record<string, unknown>;
410
+ constraintTrace?: ConstraintTrace;
411
+ explanations?: FashionSearchExplanation[];
412
+ fallback?: {
413
+ reason: "no_results" | "low_confidence";
414
+ relaxedFilters: string[];
415
+ };
416
+ debug?: Record<string, unknown>;
417
+ took_ms: number;
418
+ }
419
+ type AgentImageInput = {
420
+ kind: "url";
421
+ url: string;
422
+ } | {
423
+ kind: "bytes";
424
+ bytesBase64: string;
425
+ mimeType?: string;
426
+ } | {
427
+ kind: "product_image";
428
+ productId: string;
429
+ imageField?: string;
430
+ };
431
+ interface FindProductsRequest {
432
+ intent?: string;
433
+ image?: AgentImageInput;
434
+ constraints?: Record<string, unknown>;
435
+ shopperContext?: Record<string, unknown>;
436
+ constraintMode?: "best_effort" | "strict";
437
+ explain?: boolean;
438
+ limit?: number;
439
+ }
440
+ interface ProductVariantAvailability {
441
+ id?: string;
442
+ title?: string;
443
+ size?: string;
444
+ price?: number;
445
+ available?: boolean;
446
+ inventoryQuantity?: number;
447
+ updatedAt?: string;
448
+ }
449
+ interface ConstraintVerification {
450
+ status: "satisfied" | "violated" | "unknown";
451
+ satisfied: string[];
452
+ violated: string[];
453
+ unknown: string[];
454
+ strictExcluded?: boolean;
455
+ }
456
+ interface GroundedProductCandidate {
457
+ id: string;
458
+ title?: string;
459
+ url?: string;
460
+ imageUrl?: string;
461
+ price?: {
462
+ amount: number;
463
+ currency?: string;
464
+ lastUpdatedAt?: string;
465
+ };
466
+ availability?: {
467
+ inStock?: boolean;
468
+ variants?: ProductVariantAvailability[];
469
+ lastCheckedAt?: string;
470
+ freshness: "fresh" | "stale" | "unknown";
471
+ };
472
+ score: number;
473
+ data: Record<string, unknown>;
474
+ grounding: {
475
+ project: string;
476
+ collection: string;
477
+ productId: string;
478
+ indexedAt?: string;
479
+ sourceUpdatedAt?: string;
480
+ };
481
+ verification: ConstraintVerification;
482
+ why?: Record<string, unknown>;
483
+ }
484
+ interface FindProductsResponse {
485
+ products: GroundedProductCandidate[];
486
+ parsed?: Record<string, unknown>;
487
+ constraintTrace?: ConstraintTrace;
488
+ relaxed?: boolean;
489
+ took_ms: number;
490
+ }
491
+ interface AgentToolDescriptor {
492
+ name: "find_products" | "find_similar_products" | "compare_products" | "explain_result" | "get_product_availability" | "recover_no_results";
493
+ description: string;
494
+ inputSchema: Record<string, unknown>;
495
+ outputSchema: Record<string, unknown>;
496
+ }
311
497
  interface ProjectConfig {
312
498
  entities?: EntityDef[];
313
499
  collections?: CollectionDef[];
314
500
  }
315
501
 
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 };
502
+ 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, SchemaInput, 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.2.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
  },