@instruments/materialgraph 0.31.0 → 0.33.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.
@@ -1,306 +0,0 @@
1
- import type { ResponseAttribution } from "./attribution-types.js";
2
- import type { MatchCategoryInference, MatchColourReading } from "./match-types.js";
3
- /**
4
- * The Material Match PROGRESS-STREAM contracts — the wire vocabulary of
5
- * {@link MaterialGraphClient.matchStream} (`POST /api/v1/search/match` under
6
- * `Accept: application/x-ndjson`). One JSON object per line; the run ends on a
7
- * `complete` or an `error` event.
8
- *
9
- * These shapes are HAND-MIRRORED from `matchStreamEventSchema` in
10
- * `@materialgraph/products` — the SDK publishes with zero runtime dependencies,
11
- * so it cannot import the Zod schema itself. `contract-parity.test.ts` re-checks
12
- * the mirror against the real schema, deriving the enum members FROM the
13
- * canonical schemas so a server-side addition fails that test rather than
14
- * silently widening the client.
15
- *
16
- * Every enum here is CLOSED, exactly as the canonical `z.enum(…)` members are.
17
- * The value lists are exported so a consumer can build an exhaustive switch or a
18
- * UI legend from the same source the validator uses.
19
- */
20
- import type { SearchResultItem } from "./types.js";
21
- /** How the workflow classified the uploaded image. */
22
- export declare const MATCH_IMAGE_TYPES: readonly ["swatch", "installation", "flatplan"];
23
- export type MatchImageType = (typeof MATCH_IMAGE_TYPES)[number];
24
- /** Why a Match run ended on an `error` frame (MG-730). */
25
- export declare const MATCH_ERROR_REASONS: readonly ["upstream_unavailable", "failed"];
26
- export type MatchErrorReason = (typeof MATCH_ERROR_REASONS)[number];
27
- /** What a detected surface is FOR — the element the material would serve. */
28
- export declare const MATCH_MATERIAL_FUNCTIONS: readonly ["flooring", "wall_surface", "ceiling", "partition", "upholstery", "millwork", "countertop", "accent", "sample"];
29
- export type MatchMaterialFunction = (typeof MATCH_MATERIAL_FUNCTIONS)[number];
30
- /** The workflow's coarse progress phase, narrated by a `step` event. */
31
- export declare const MATCH_WORKFLOW_STEPS: readonly ["classifying", "decomposing", "matching", "complete"];
32
- export type MatchWorkflowStep = (typeof MATCH_WORKFLOW_STEPS)[number];
33
- /**
34
- * Where one surface stands in the run.
35
- *
36
- * `colour_fallback` (appended, additive): the surface completed, but through
37
- * the colour lane — its visible sample was too small to read texture, so the
38
- * results match the region's palette, scoped to the detected category. Such a
39
- * surface carries a `note` saying so and no `senseCheck` verdicts (there is no
40
- * texture to verify).
41
- */
42
- export declare const MATCH_SURFACE_STATUSES: readonly ["pending", "matching", "complete", "error", "colour_fallback"];
43
- export type MatchSurfaceStatus = (typeof MATCH_SURFACE_STATUSES)[number];
44
- /** Every frame type the stream can carry — the union's discriminant values. */
45
- export declare const MATCH_STREAM_EVENT_TYPES: readonly ["step", "classified", "surfaces", "surface", "heartbeat", "complete", "error"];
46
- export type MatchStreamEventType = (typeof MATCH_STREAM_EVENT_TYPES)[number];
47
- /** Where a surface sits in the image, in Gemini-style 0–1000 image space. */
48
- export type MatchBoundingBox = {
49
- x0: number;
50
- y0: number;
51
- x1: number;
52
- y1: number;
53
- };
54
- /** One surface the decompose step found in the image. */
55
- export type MatchDetectedSurface = {
56
- id: string;
57
- label: string;
58
- materialFunction: MatchMaterialFunction;
59
- /** The detector's catalog category answer, when it named one. */
60
- categoryHint?: string;
61
- /** Detector confidence, always within [0, 1]. */
62
- confidence: number;
63
- description?: string;
64
- boundingBox: MatchBoundingBox;
65
- /** JPEG data-URI crop used for Match retrieval — the thumbnail to show. */
66
- cropDataUri?: string;
67
- /** The `construction_methods` dictionary term the detector's finish names. */
68
- construction?: string;
69
- };
70
- /**
71
- * One surface's matching state and its ranked results. The same `surface.id`
72
- * arrives repeatedly as the run progresses (`matching` → `complete` / `error`);
73
- * render by replacing on id rather than appending.
74
- *
75
- * A `complete` surface can arrive TWICE: the first frame delivers its finished
76
- * results as soon as they exist, and a second repeats them unchanged with the
77
- * verifier's per-candidate verdicts attached (`senseCheck`).
78
- * Replacing on id handles both; the results never change between them, so
79
- * nothing a user is looking at moves when the second lands.
80
- *
81
- * `results` and `inferredCategories` are always present on a yielded event: the
82
- * canonical schema defaults each to `[]`, and the reader fills them when the
83
- * server omits them, so neither ever arrives as `undefined`.
84
- */
85
- /**
86
- * What a Match actually compared, so a consumer can tell "the catalogue holds
87
- * nothing close" from "everything ranked badly" (MG-1031). Without it both
88
- * arrive as an empty list and the difference is unrecoverable.
89
- *
90
- * UNITS: `qualityFloor` and `bestRejected` are absolute image-arm cosines in
91
- * [0,1] — the same unit as each result's `confidence` — so they belong on one
92
- * axis. `poolSize` and `droppedBelowFloor` are counts and belong on neither.
93
- */
94
- export type MatchComparison = {
95
- /** Candidates retrieved before the quality floor and product dedup. */
96
- poolSize: number;
97
- /** How many of those the quality floor rejected. */
98
- droppedBelowFloor: number;
99
- /** The floor they were held to, as an absolute image cosine in [0,1]. */
100
- qualityFloor: number;
101
- /**
102
- * The closest REJECTED candidate, same unit as `qualityFloor`. Absent when
103
- * nothing was rejected, or when the rejects carried no readable signal.
104
- */
105
- bestRejected?: number;
106
- };
107
- export type MatchSurfaceResult = {
108
- surface: MatchDetectedSurface;
109
- status: MatchSurfaceStatus;
110
- results: SearchResultItem[];
111
- inferredCategories: MatchCategoryInference[];
112
- /** What this surface compared against. Absent means the server did not report it. */
113
- comparison?: MatchComparison;
114
- /**
115
- * When `results` is empty, WHICH STAGE is responsible. Absent when the
116
- * surface returned rows, and absent on servers older than this field.
117
- *
118
- * - `empty_pool` — nothing eligible was retrieved. A fact about the
119
- * catalogue under the corpus scope in force.
120
- * - `quality_floor` — candidates WERE retrieved and every one was rejected.
121
- * A fact about the engine, not the catalogue.
122
- * - `unattributed` — no comparison receipt survived to attribute from.
123
- *
124
- * The distinction is the point: "we found nothing you can buy" and "we found
125
- * things and declined them all" are the same empty list and different
126
- * findings, and only the first belongs on a "nothing available here yet"
127
- * state.
128
- */
129
- emptyReason?: "empty_pool" | "quality_floor" | "unattributed";
130
- /**
131
- * The verifier's reading of each candidate, when it ran.
132
- *
133
- * ABSENT means the stage did not run — it is opt-out, and the swatch route
134
- * never runs it — OR that it is still running, which `senseCheckPending`
135
- * distinguishes. Neither is the same as every candidate abstaining, and a
136
- * consumer must not conflate them.
137
- *
138
- * `abstained` is NOT a soft rejection: it means the candidate was not judged
139
- * (no image, no answer, or the judge was unavailable). Never render an
140
- * abstention as a negative signal about the material.
141
- */
142
- senseCheck?: MatchSenseCheckCell[];
143
- /**
144
- * TRUE on the interim frame that delivers finished results while the verifier
145
- * is still reading them. Render the rows immediately. The follow-up frame
146
- * carries the SAME rows with verdicts attached, settled once into verdict
147
- * bands (confirmed → abstained → rejected, stable within each band) — the
148
- * page's one settling reorder. Nothing is ever removed.
149
- */
150
- senseCheckPending?: boolean;
151
- /**
152
- * Per-candidate colour readings against this surface's own crop, when the
153
- * colour re-rank ran. `results` are already ordered by the blended score when
154
- * this is present; this array is the receipt for that order.
155
- */
156
- colour?: MatchColourReading[];
157
- /** The segment's own extracted palette — what the colour stage matched against. */
158
- segmentPalette?: {
159
- hex: string;
160
- proportion: number;
161
- }[];
162
- /**
163
- * One sentence of provenance a consumer may render verbatim, when the server
164
- * sent one — e.g. the `colour_fallback` explanation carrying the refused
165
- * sample's pixel size. Absent on ordinary surfaces.
166
- */
167
- note?: string;
168
- /** Present only on `status: "error"` — why this one surface failed. */
169
- error?: string;
170
- };
171
- /** The verifier's reading of one candidate, keyed back to its variant. */
172
- export type MatchSenseCheckCell = {
173
- variantId: string;
174
- verdict: MatchSenseCheckVerdict;
175
- reason: string;
176
- };
177
- export declare const MATCH_SENSE_CHECK_VERDICTS: readonly ["confirmed", "rejected", "abstained"];
178
- export type MatchSenseCheckVerdict = (typeof MATCH_SENSE_CHECK_VERDICTS)[number];
179
- /** The terminal Match workflow result — every surface in its final state. */
180
- /**
181
- * How a Match run ended. `no_surfaces` means the detector worked and found no
182
- * material in the photo — a finding, not a failure. Branch on this rather than
183
- * on an error message. Absent means `matched`.
184
- */
185
- export declare const MATCH_OUTCOMES: readonly ["matched", "no_surfaces"];
186
- export type MatchOutcome = (typeof MATCH_OUTCOMES)[number];
187
- export type MatchWorkflowResult = {
188
- /**
189
- * Result attribution (MG-1131) — `responseId` (the same value as the run's
190
- * `X-Request-Id` header), `basis: "match"`, and `issuedAt`. Every item in
191
- * `results` and in each `surfaces[].results` carries a matching `resultId`.
192
- *
193
- * A surface's hits arrive twice — on its own `surface` frame and again here —
194
- * and both times with the SAME `resultId`, so a consumer that stored the
195
- * first recognises the second rather than double-counting it.
196
- *
197
- * ABSENT only from a response produced by a server predating MG-1131.
198
- */
199
- attribution?: ResponseAttribution;
200
- imageType: MatchImageType;
201
- /** How the run ended. Absent means `matched`. */
202
- outcome?: MatchOutcome;
203
- surfaces: MatchSurfaceResult[];
204
- /** Present for swatch / single-surface runs that skip decompose. */
205
- results?: SearchResultItem[];
206
- inferredCategories?: MatchCategoryInference[];
207
- /**
208
- * How the run ended, counted rather than inferred. `surfacesFailed > 0` with
209
- * no results is a broken run; zero failures with no results is a genuine
210
- * "nothing above the floor". Do not fold `surfaces[]` to tell those apart.
211
- */
212
- totals: MatchRunTotals;
213
- /**
214
- * Whole-image palette matches, sent ONLY when an installation photo yielded
215
- * no usable surfaces at all. These match the image's overall palette — they
216
- * are not material identifications, and the note says so. A consumer that
217
- * ignores this field sees exactly the empty run it saw before the field
218
- * existed.
219
- */
220
- paletteMatches?: {
221
- results: SearchResultItem[];
222
- palette: {
223
- hex: string;
224
- proportion: number;
225
- }[];
226
- note: string;
227
- };
228
- /**
229
- * What a SWATCH run compared against — a swatch detects no surfaces, so it has
230
- * nowhere else to put this. A scene run reports per surface instead.
231
- */
232
- comparison?: MatchComparison;
233
- };
234
- /** Progress narration for the phase the workflow just entered. */
235
- export type MatchStreamStepEvent = {
236
- type: "step";
237
- step: MatchWorkflowStep;
238
- message: string;
239
- };
240
- /** The image type the classify step settled on. */
241
- export type MatchStreamClassifiedEvent = {
242
- type: "classified";
243
- imageType: MatchImageType;
244
- };
245
- /** Every surface the decompose step found, before any of them has matched. */
246
- export type MatchStreamSurfacesEvent = {
247
- type: "surfaces";
248
- surfaces: MatchDetectedSurface[];
249
- };
250
- /**
251
- * The run's tally at the moment a `surface` event was emitted, so a consumer
252
- * reads the server's arithmetic rather than reimplementing it.
253
- *
254
- * A FAILED surface is the case this exists to settle: failing is a way of
255
- * RESOLVING. An errored surface counts in `surfacesResolved` (it will never be
256
- * reported again), counts again in `surfacesFailed`, and adds nothing to
257
- * `matchesSoFar`. `surfacesResolved === surfacesDetected` is therefore the
258
- * correct "the run is done" test, and never stalls at 3/4 on a failure.
259
- */
260
- export type MatchRunTotals = {
261
- /** Surfaces the decompose step found. The denominator; fixed for the run. */
262
- surfacesDetected: number;
263
- /** Surfaces that reached a terminal state — `complete` OR `error`. */
264
- surfacesResolved: number;
265
- /** Of those resolved, how many ended in `error`. Always ≤ `surfacesResolved`. */
266
- surfacesFailed: number;
267
- /** Result items accumulated across every surface resolved so far. */
268
- matchesSoFar: number;
269
- };
270
- /** One surface's state changed — the frame that makes Match progressive. */
271
- export type MatchStreamSurfaceEvent = {
272
- type: "surface";
273
- surface: MatchSurfaceResult;
274
- /** The run's tally as of this frame. Always present on a `surface` event. */
275
- totals: MatchRunTotals;
276
- };
277
- /**
278
- * CONNECTION LIVENESS ONLY — never progress. Proxies and load balancers kill
279
- * idle connections, so the server sends this when the stream has been quiet, and
280
- * a client can tell a slow run from a dropped one. It carries no payload, and no
281
- * consumer should treat it as a state change: do not advance a step, reset a
282
- * no-progress watchdog, or count it towards anything.
283
- */
284
- export type MatchStreamHeartbeatEvent = {
285
- type: "heartbeat";
286
- };
287
- /** Terminal: the whole run finished and carries its assembled result. */
288
- export type MatchStreamCompleteEvent = {
289
- type: "complete";
290
- result: MatchWorkflowResult;
291
- };
292
- /** Terminal: the run failed as a whole (a single surface failing is not this). */
293
- export type MatchStreamErrorEvent = {
294
- type: "error";
295
- message: string;
296
- /**
297
- * `upstream_unavailable`: a provider (vision, embedding, vector index) could
298
- * not answer, so nothing was looked at and a retry may succeed. `failed`: the
299
- * run broke on something else. Absent from servers older than MG-730.
300
- */
301
- reason?: MatchErrorReason;
302
- /** True only for `upstream_unavailable`. Absent from servers older than MG-730. */
303
- retriable?: boolean;
304
- };
305
- /** One frame of the Match progress stream, discriminated by `type`. */
306
- export type MatchStreamEvent = MatchStreamStepEvent | MatchStreamClassifiedEvent | MatchStreamSurfacesEvent | MatchStreamSurfaceEvent | MatchStreamHeartbeatEvent | MatchStreamCompleteEvent | MatchStreamErrorEvent;
@@ -1,65 +0,0 @@
1
- /** How the workflow classified the uploaded image. */
2
- export const MATCH_IMAGE_TYPES = [
3
- "swatch",
4
- "installation",
5
- "flatplan",
6
- ];
7
- /** Why a Match run ended on an `error` frame (MG-730). */
8
- export const MATCH_ERROR_REASONS = ["upstream_unavailable", "failed"];
9
- /** What a detected surface is FOR — the element the material would serve. */
10
- export const MATCH_MATERIAL_FUNCTIONS = [
11
- "flooring",
12
- "wall_surface",
13
- "ceiling",
14
- "partition",
15
- "upholstery",
16
- "millwork",
17
- "countertop",
18
- "accent",
19
- "sample",
20
- ];
21
- /** The workflow's coarse progress phase, narrated by a `step` event. */
22
- export const MATCH_WORKFLOW_STEPS = [
23
- "classifying",
24
- "decomposing",
25
- "matching",
26
- "complete",
27
- ];
28
- /**
29
- * Where one surface stands in the run.
30
- *
31
- * `colour_fallback` (appended, additive): the surface completed, but through
32
- * the colour lane — its visible sample was too small to read texture, so the
33
- * results match the region's palette, scoped to the detected category. Such a
34
- * surface carries a `note` saying so and no `senseCheck` verdicts (there is no
35
- * texture to verify).
36
- */
37
- export const MATCH_SURFACE_STATUSES = [
38
- "pending",
39
- "matching",
40
- "complete",
41
- "error",
42
- "colour_fallback",
43
- ];
44
- /** Every frame type the stream can carry — the union's discriminant values. */
45
- export const MATCH_STREAM_EVENT_TYPES = [
46
- "step",
47
- "classified",
48
- "surfaces",
49
- "surface",
50
- "heartbeat",
51
- "complete",
52
- "error",
53
- ];
54
- export const MATCH_SENSE_CHECK_VERDICTS = [
55
- "confirmed",
56
- "rejected",
57
- "abstained",
58
- ];
59
- /** The terminal Match workflow result — every surface in its final state. */
60
- /**
61
- * How a Match run ended. `no_surfaces` means the detector worked and found no
62
- * material in the photo — a finding, not a failure. Branch on this rather than
63
- * on an error message. Absent means `matched`.
64
- */
65
- export const MATCH_OUTCOMES = ["matched", "no_surfaces"];
package/match-stream.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { HttpTransport } from "./http-transport.js";
2
- import type { MatchStreamEvent } from "./match-stream-types.js";
3
- import type { MatchInput } from "./match-types.js";
4
- /**
5
- * Decode one NDJSON line into a validated {@link MatchStreamEvent}, raising a
6
- * {@link MaterialGraphError} on anything the contract does not allow. Exported
7
- * for `contract-parity.test.ts`, which re-checks it against the real Zod schema;
8
- * it is not part of the published surface.
9
- */
10
- export declare const decodeMatchStreamEvent: (line: string) => MatchStreamEvent;
11
- /**
12
- * Stream Match progress as an async iterator. Iterates until the run reaches a
13
- * `complete` or an `error` event, and raises if the connection ends before one
14
- * arrives.
15
- *
16
- * `signal` genuinely cancels: it is linked to the AbortController that owns the
17
- * in-flight request, so aborting (or simply `break`ing out of the loop) tears
18
- * down the HTTP connection rather than merely stopping the yields. A
19
- * caller-requested abort ends the iteration CLEANLY — the caller already knows
20
- * it cancelled, so the truncated-stream error is not raised for it.
21
- */
22
- export declare function matchStream(transport: HttpTransport, input: MatchInput, signal?: AbortSignal): AsyncGenerator<MatchStreamEvent, void, unknown>;