@instruments/materialgraph 0.1.0 → 0.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/client.d.ts +141 -7
- package/client.js +185 -125
- package/codes-types.d.ts +93 -0
- package/codes-types.js +1 -0
- package/composition-types.d.ts +117 -0
- package/composition-types.js +10 -0
- package/http-transport.d.ts +57 -0
- package/http-transport.js +131 -0
- package/index.d.ts +12 -0
- package/index.js +6 -0
- package/operations.d.ts +6 -2
- package/operations.js +24 -2
- package/package.json +1 -1
- package/project-types.d.ts +67 -0
- package/project-types.js +16 -0
- package/projects.d.ts +13 -0
- package/projects.js +46 -0
- package/run-events.d.ts +82 -0
- package/run-events.js +29 -0
- package/run-types.d.ts +202 -0
- package/run-types.js +6 -0
- package/runs.d.ts +42 -0
- package/runs.js +214 -0
- package/search-refinement-types.d.ts +13 -1
- package/spec-types.d.ts +78 -0
- package/spec-types.js +40 -0
- package/specifications.d.ts +63 -0
- package/specifications.js +73 -0
- package/sse.d.ts +35 -0
- package/sse.js +147 -0
- package/types.d.ts +145 -4
package/run-events.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed payloads for the composition Run event family — the friction fix flagged
|
|
3
|
+
* by the MG-443 walk. These narrow the loose {@link RunEvent.payloadJson} so
|
|
4
|
+
* consumers (the Box console) can render phase progress, the parsed programme,
|
|
5
|
+
* and the criteria-correction UI without re-typing each payload at the call
|
|
6
|
+
* site.
|
|
7
|
+
*
|
|
8
|
+
* Both wire generations are covered: the CURRENT `composition.*` strings the
|
|
9
|
+
* server emits today, and the LEGACY `material-box.*` strings kept for read-side
|
|
10
|
+
* dual-read compatibility. Shapes mirror
|
|
11
|
+
* `packages/mastra/src/workflow/composition/events.ts` and the
|
|
12
|
+
* `reviewCriteriaSuspendPayloadSchema` in `@materialgraph/products` — expressed
|
|
13
|
+
* as standalone TS types (no cross-package import; the SDK is publishable).
|
|
14
|
+
*/
|
|
15
|
+
import type { OpenEnum } from "./types.js";
|
|
16
|
+
import type { RunEvent } from "./run-types.js";
|
|
17
|
+
export declare const COMPOSITION_PHASE_EVENT_TYPES: readonly ["composition.phase", "material-box.phase"];
|
|
18
|
+
export declare const COMPOSITION_PROGRAMME_EVENT_TYPES: readonly ["composition.programme", "material-box.brief"];
|
|
19
|
+
export declare const COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES: readonly ["composition.review-criteria", "material-box.review-criteria"];
|
|
20
|
+
export type CompositionPhaseEventType = (typeof COMPOSITION_PHASE_EVENT_TYPES)[number];
|
|
21
|
+
export type CompositionProgrammeEventType = (typeof COMPOSITION_PROGRAMME_EVENT_TYPES)[number];
|
|
22
|
+
export type CompositionReviewCriteriaEventType = (typeof COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES)[number];
|
|
23
|
+
/** Payload of a `composition.phase` / `material-box.phase` progress event. */
|
|
24
|
+
export interface CompositionPhasePayload {
|
|
25
|
+
workflow: OpenEnum<"parse-material-brief" | "compose-material-schemes">;
|
|
26
|
+
phase: string;
|
|
27
|
+
label: string;
|
|
28
|
+
state: OpenEnum<"started" | "completed" | "failed">;
|
|
29
|
+
/** Additive narration (file names, error messages); absent on older events. */
|
|
30
|
+
detail?: string;
|
|
31
|
+
/** Additive tallies (slot/candidate/scheme counts); absent on older events. */
|
|
32
|
+
counts?: Record<string, number>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Payload of a `composition.programme` / `material-box.brief` event. The current
|
|
36
|
+
* generation carries `programme`; the legacy generation carried `brief`. Both
|
|
37
|
+
* are read structurally (the parsed programme document).
|
|
38
|
+
*/
|
|
39
|
+
export interface CompositionProgrammePayload {
|
|
40
|
+
programme?: unknown;
|
|
41
|
+
brief?: unknown;
|
|
42
|
+
}
|
|
43
|
+
/** One unresolved brief phrase offered for human correction at the review step. */
|
|
44
|
+
export interface CompositionReviewCriteriaSuggestion {
|
|
45
|
+
attributeId: string;
|
|
46
|
+
name: string;
|
|
47
|
+
term?: string;
|
|
48
|
+
score: number;
|
|
49
|
+
}
|
|
50
|
+
export interface CompositionReviewCriteriaUnresolved {
|
|
51
|
+
slotId: string;
|
|
52
|
+
raw: string;
|
|
53
|
+
suggestions: CompositionReviewCriteriaSuggestion[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Payload of a `composition.review-criteria` / `material-box.review-criteria`
|
|
57
|
+
* suspend event — the correction grammar the Box review step renders.
|
|
58
|
+
*/
|
|
59
|
+
export interface CompositionReviewCriteriaPayload {
|
|
60
|
+
unresolved: CompositionReviewCriteriaUnresolved[];
|
|
61
|
+
}
|
|
62
|
+
export interface CompositionPhaseEvent extends RunEvent {
|
|
63
|
+
type: CompositionPhaseEventType;
|
|
64
|
+
payloadJson: CompositionPhasePayload;
|
|
65
|
+
}
|
|
66
|
+
export interface CompositionProgrammeEvent extends RunEvent {
|
|
67
|
+
type: CompositionProgrammeEventType;
|
|
68
|
+
payloadJson: CompositionProgrammePayload;
|
|
69
|
+
}
|
|
70
|
+
export interface CompositionReviewCriteriaEvent extends RunEvent {
|
|
71
|
+
type: CompositionReviewCriteriaEventType;
|
|
72
|
+
payloadJson: CompositionReviewCriteriaPayload;
|
|
73
|
+
}
|
|
74
|
+
/** The discriminated union of all composition Run events. */
|
|
75
|
+
export type CompositionEvent = CompositionPhaseEvent | CompositionProgrammeEvent | CompositionReviewCriteriaEvent;
|
|
76
|
+
/**
|
|
77
|
+
* Narrow a {@link RunEvent} to the composition family by its `type`, returning
|
|
78
|
+
* `null` for lifecycle events (`queued`, `running`, `succeeded`, …) and any
|
|
79
|
+
* unknown type. Narrowing is by the discriminating `type` only — payloads are
|
|
80
|
+
* trusted structurally per the tolerant-parsing contract, never re-validated.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseRunEvent(event: RunEvent): CompositionEvent | null;
|
package/run-events.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ── Wire event-type strings (both generations) ───────────────────────────────
|
|
2
|
+
export const COMPOSITION_PHASE_EVENT_TYPES = ["composition.phase", "material-box.phase"];
|
|
3
|
+
export const COMPOSITION_PROGRAMME_EVENT_TYPES = [
|
|
4
|
+
"composition.programme",
|
|
5
|
+
"material-box.brief",
|
|
6
|
+
];
|
|
7
|
+
export const COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES = [
|
|
8
|
+
"composition.review-criteria",
|
|
9
|
+
"material-box.review-criteria",
|
|
10
|
+
];
|
|
11
|
+
const includesType = (types, type) => types.includes(type);
|
|
12
|
+
/**
|
|
13
|
+
* Narrow a {@link RunEvent} to the composition family by its `type`, returning
|
|
14
|
+
* `null` for lifecycle events (`queued`, `running`, `succeeded`, …) and any
|
|
15
|
+
* unknown type. Narrowing is by the discriminating `type` only — payloads are
|
|
16
|
+
* trusted structurally per the tolerant-parsing contract, never re-validated.
|
|
17
|
+
*/
|
|
18
|
+
export function parseRunEvent(event) {
|
|
19
|
+
if (includesType(COMPOSITION_PHASE_EVENT_TYPES, event.type)) {
|
|
20
|
+
return event;
|
|
21
|
+
}
|
|
22
|
+
if (includesType(COMPOSITION_PROGRAMME_EVENT_TYPES, event.type)) {
|
|
23
|
+
return event;
|
|
24
|
+
}
|
|
25
|
+
if (includesType(COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES, event.type)) {
|
|
26
|
+
return event;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
package/run-types.d.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-mirrored wire types for the MaterialGraph public Run + source-material
|
|
3
|
+
* surface (spec 0.7.0). Like {@link ./types.ts}, these interfaces carry NO
|
|
4
|
+
* runtime dependency on any `@materialgraph/*` package — they mirror the shapes
|
|
5
|
+
* in `apps/web/public/openapi.yaml` (`list_public_capabilities`,
|
|
6
|
+
* `create_public_run`, `get_public_run`, `list_public_run_events`,
|
|
7
|
+
* `resume_public_run`, `cancel_public_run`, `upload_source_material`).
|
|
8
|
+
*
|
|
9
|
+
* TOLERANT-PARSING CONTRACT: response enums are widened with {@link OpenEnum}
|
|
10
|
+
* so a new server value still type-checks, and objects are read structurally —
|
|
11
|
+
* unknown keys pass through untouched.
|
|
12
|
+
*/
|
|
13
|
+
import type { OpenEnum } from "./types.js";
|
|
14
|
+
/**
|
|
15
|
+
* A file to upload. Two forms so the SDK is ergonomic on BOTH Node route
|
|
16
|
+
* handlers and modern runtimes: pass a platform `Blob`/`File` directly, or the
|
|
17
|
+
* bytes plus a filename. The SDK builds the multipart body internally — callers
|
|
18
|
+
* never touch `FormData` or wrestle a `string`-typed binary field.
|
|
19
|
+
*/
|
|
20
|
+
export type FileInput = Blob | {
|
|
21
|
+
/** Raw file bytes. */
|
|
22
|
+
data: Uint8Array | ArrayBuffer;
|
|
23
|
+
/** Filename recorded on the multipart part (drives the stored receipt filename). */
|
|
24
|
+
filename: string;
|
|
25
|
+
/** Optional MIME type; the server re-validates by magic bytes regardless. */
|
|
26
|
+
contentType?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A heterogeneous pile of source files (multipart field `files`, repeatable):
|
|
30
|
+
* any mix of PDF, plain-text/markdown, and images (≥1, ≤16). The server
|
|
31
|
+
* classifies each file and assigns its receipt {@link SourceMaterialReceiptFile.role};
|
|
32
|
+
* the caller no longer pre-sorts a brief from its references.
|
|
33
|
+
*/
|
|
34
|
+
export interface UploadSourceMaterialFilesInput {
|
|
35
|
+
files: FileInput[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The legacy pre-classified shape — a single PDF `brief` plus optional image
|
|
39
|
+
* `references` (multipart fields `brief` + `references`). Still accepted by the
|
|
40
|
+
* server, folded into the pile. Prefer {@link UploadSourceMaterialFilesInput}.
|
|
41
|
+
*/
|
|
42
|
+
export interface UploadSourceMaterialLegacyInput {
|
|
43
|
+
/** The single PDF brief for the batch (multipart field `brief`). */
|
|
44
|
+
brief: FileInput;
|
|
45
|
+
/** Up to 12 reference images (multipart field `references`, repeatable). */
|
|
46
|
+
references?: FileInput[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Input for {@link MaterialGraphClient.uploadSourceMaterial} — either the
|
|
50
|
+
* pile-of-files shape or the legacy brief+references shape.
|
|
51
|
+
*/
|
|
52
|
+
export type UploadSourceMaterialInput = UploadSourceMaterialFilesInput | UploadSourceMaterialLegacyInput;
|
|
53
|
+
/** One stored file in an upload receipt. */
|
|
54
|
+
export interface SourceMaterialReceiptFile {
|
|
55
|
+
/** The stored file id (`smf_…`). */
|
|
56
|
+
sourceId: string;
|
|
57
|
+
/**
|
|
58
|
+
* The pipeline-assigned role. The vocabulary grows over time; {@link OpenEnum}
|
|
59
|
+
* keeps a newer server value type-checking. `unclassified` is a legitimate
|
|
60
|
+
* terminal value when the pipeline could not place the file.
|
|
61
|
+
*/
|
|
62
|
+
role: OpenEnum<"brief" | "reference_image" | "document" | "document_image" | "inspiration_image" | "unclassified">;
|
|
63
|
+
filename: string;
|
|
64
|
+
mediaType: string;
|
|
65
|
+
/** Lowercase hex SHA-256 of the stored bytes. */
|
|
66
|
+
sha256: string;
|
|
67
|
+
bytes: number;
|
|
68
|
+
/** PDF page count; null for images. */
|
|
69
|
+
pages: number | null;
|
|
70
|
+
}
|
|
71
|
+
/** The `201` receipt from `POST /api/v1/source-material`. */
|
|
72
|
+
export interface SourceMaterialBatchReceipt {
|
|
73
|
+
/** The stored batch id (`smb_…`). */
|
|
74
|
+
batchId: string;
|
|
75
|
+
sources: SourceMaterialReceiptFile[];
|
|
76
|
+
}
|
|
77
|
+
export type CapabilityKind = OpenEnum<"agent" | "tool" | "workflow">;
|
|
78
|
+
export type CapabilityExposure = OpenEnum<"public" | "partner" | "internal">;
|
|
79
|
+
export type CapabilityScope = OpenEnum<"mg:read" | "mg:write" | "mg:ingest" | "mg:admin">;
|
|
80
|
+
export type CapabilitySideEffectClass = OpenEnum<"read_only" | "browser_local" | "external_read" | "writes_proposed" | "writes_persisted" | "planned_only">;
|
|
81
|
+
export interface CapabilityRetryPolicy {
|
|
82
|
+
maxAttempts: number;
|
|
83
|
+
backoff: OpenEnum<"none" | "fixed" | "exponential">;
|
|
84
|
+
}
|
|
85
|
+
export interface CapabilityInvokeLink {
|
|
86
|
+
href: string;
|
|
87
|
+
method: OpenEnum<"GET" | "POST">;
|
|
88
|
+
}
|
|
89
|
+
/** One invocable capability from the governed public manifest. */
|
|
90
|
+
export interface PublicCapabilityMetadata {
|
|
91
|
+
publicId: string;
|
|
92
|
+
runtimeId: string;
|
|
93
|
+
kind: CapabilityKind;
|
|
94
|
+
exposure: CapabilityExposure;
|
|
95
|
+
name: string;
|
|
96
|
+
description: string;
|
|
97
|
+
schemas: {
|
|
98
|
+
input: Record<string, unknown>;
|
|
99
|
+
output: Record<string, unknown>;
|
|
100
|
+
};
|
|
101
|
+
sideEffectClass: CapabilitySideEffectClass;
|
|
102
|
+
scopes: CapabilityScope[];
|
|
103
|
+
timeoutMs: number;
|
|
104
|
+
retryPolicy: CapabilityRetryPolicy;
|
|
105
|
+
tags: string[];
|
|
106
|
+
links: {
|
|
107
|
+
invoke: CapabilityInvokeLink;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The `GET /api/v1/capabilities` response envelope. */
|
|
111
|
+
export interface CapabilityManifestResponse {
|
|
112
|
+
capabilities: PublicCapabilityMetadata[];
|
|
113
|
+
}
|
|
114
|
+
export type PublicRunStatus = OpenEnum<"queued" | "claimed" | "running" | "suspended" | "succeeded" | "failed" | "canceled">;
|
|
115
|
+
export interface PublicRunFailure {
|
|
116
|
+
code: string;
|
|
117
|
+
message: string;
|
|
118
|
+
retryable: boolean;
|
|
119
|
+
detail?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
export interface PublicRunArtifactRef {
|
|
122
|
+
kind: string;
|
|
123
|
+
ref: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The caller-owned, redacted Run ledger record. Prompts, raw model messages,
|
|
127
|
+
* reasoning, credentials, provider payloads, and source-document contents are
|
|
128
|
+
* never returned — inputs and outputs are the `redacted*Json` projections.
|
|
129
|
+
*/
|
|
130
|
+
export interface PublicRun {
|
|
131
|
+
id: string;
|
|
132
|
+
capabilityKind: CapabilityKind;
|
|
133
|
+
capabilityId: string;
|
|
134
|
+
clientReference: string | null;
|
|
135
|
+
status: PublicRunStatus;
|
|
136
|
+
redactedInputJson: Record<string, unknown>;
|
|
137
|
+
redactedOutputJson: Record<string, unknown> | null;
|
|
138
|
+
usageJson: Record<string, unknown> | null;
|
|
139
|
+
artifactRefs: PublicRunArtifactRef[] | null;
|
|
140
|
+
failureJson: PublicRunFailure | null;
|
|
141
|
+
suspensionStepId: string | null;
|
|
142
|
+
eventCursor: number;
|
|
143
|
+
attempts: number;
|
|
144
|
+
startedAt: string | null;
|
|
145
|
+
cancellationRequestedAt: string | null;
|
|
146
|
+
completedAt: string | null;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
updatedAt: string;
|
|
149
|
+
}
|
|
150
|
+
/** Body for `POST /api/v1/runs` ({@link MaterialGraphClient.createRun}). */
|
|
151
|
+
export interface CreatePublicRunRequest {
|
|
152
|
+
capabilityKind: CapabilityKind;
|
|
153
|
+
capabilityId: string;
|
|
154
|
+
/** The capability input, validated server-side against its live input schema. */
|
|
155
|
+
inputJson: unknown;
|
|
156
|
+
/** Stable caller key making Run creation idempotent across retries. */
|
|
157
|
+
idempotencyKey?: string;
|
|
158
|
+
/** Free-form caller correlation reference, echoed back on the Run. */
|
|
159
|
+
clientReference?: string;
|
|
160
|
+
}
|
|
161
|
+
/** The `POST /api/v1/runs` response (`200` replay or `202` accepted). */
|
|
162
|
+
export interface CreatePublicRunResponse {
|
|
163
|
+
accepted: boolean;
|
|
164
|
+
replayed: boolean;
|
|
165
|
+
workflowRunId: string;
|
|
166
|
+
run: PublicRun;
|
|
167
|
+
}
|
|
168
|
+
/** The `GET /api/v1/runs/{runId}` and resume/cancel response envelope. */
|
|
169
|
+
export interface PublicRunResponse {
|
|
170
|
+
run: PublicRun;
|
|
171
|
+
}
|
|
172
|
+
/** Body for `POST /api/v1/runs/{runId}/resume`. */
|
|
173
|
+
export interface ResumePublicRunRequest {
|
|
174
|
+
/** The resume payload, validated against the suspended step's resume schema. */
|
|
175
|
+
resumeData: unknown;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* One redacted Run event, decoded from an SSE `data:` frame. The stream yields
|
|
179
|
+
* these in ascending {@link RunEvent.sequence} order. `payloadJson` is read
|
|
180
|
+
* structurally; use {@link parseRunEvent} to narrow the composition family.
|
|
181
|
+
*/
|
|
182
|
+
export interface RunEvent {
|
|
183
|
+
sequence: number;
|
|
184
|
+
type: string;
|
|
185
|
+
payloadJson: unknown;
|
|
186
|
+
}
|
|
187
|
+
/** Run event types that close the stream — a terminal event ends a drain. */
|
|
188
|
+
export declare const TERMINAL_RUN_EVENT_TYPES: ReadonlySet<string>;
|
|
189
|
+
/** Options for {@link MaterialGraphClient.streamRunEvents}. */
|
|
190
|
+
export interface StreamRunEventsOptions {
|
|
191
|
+
/** Resume the stream after this sequence (exclusive). Omitted starts at 0. */
|
|
192
|
+
after?: number;
|
|
193
|
+
/** Abort the stream; iteration stops cleanly. */
|
|
194
|
+
signal?: AbortSignal;
|
|
195
|
+
}
|
|
196
|
+
/** Options for {@link MaterialGraphClient.drainRunEvents}. */
|
|
197
|
+
export interface DrainRunEventsOptions {
|
|
198
|
+
/** Resume after this sequence (exclusive). */
|
|
199
|
+
after?: number;
|
|
200
|
+
/** Stop collecting after this many milliseconds even without a terminal event. */
|
|
201
|
+
deadlineMs: number;
|
|
202
|
+
}
|
package/run-types.js
ADDED
package/runs.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HttpTransport } from "./http-transport.js";
|
|
2
|
+
import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type PublicRunResponse, type RunEvent, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Upload one atomic source-material batch (`POST /api/v1/source-material`).
|
|
5
|
+
* Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
|
|
6
|
+
* the legacy `{ brief, references }` shape (→ `brief` + repeatable `references`
|
|
7
|
+
* fields). Both wire to multipart the server accepts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function uploadSourceMaterial(transport: HttpTransport, input: UploadSourceMaterialInput, signal?: AbortSignal): Promise<SourceMaterialBatchReceipt>;
|
|
10
|
+
/** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
|
|
11
|
+
export declare function capabilities(transport: HttpTransport, signal?: AbortSignal): Promise<CapabilityManifestResponse>;
|
|
12
|
+
/** Start an asynchronous capability Run (`POST /api/v1/runs`). */
|
|
13
|
+
export declare function createRun(transport: HttpTransport, body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
|
|
14
|
+
/** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
|
|
15
|
+
export declare function getRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
16
|
+
/** Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`). */
|
|
17
|
+
export declare function resumeRun(transport: HttpTransport, runId: string, resumeData: unknown, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
18
|
+
/** Request durable Run cancellation (`POST /api/v1/runs/{runId}/cancel`). */
|
|
19
|
+
export declare function cancelRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
20
|
+
/**
|
|
21
|
+
* Stream ordered Run events as an async iterator (`GET
|
|
22
|
+
* /api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or
|
|
23
|
+
* `signal` aborts. One connection is bounded server-side (~25s or a terminal
|
|
24
|
+
* event); pass `after` to resume from the last observed sequence.
|
|
25
|
+
*/
|
|
26
|
+
export declare function streamRunEvents(transport: HttpTransport, runId: string, options?: StreamRunEventsOptions): AsyncGenerator<RunEvent, void, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Collect Run events until a terminal event, the deadline, or the stream ends —
|
|
29
|
+
* the bounded events-log pattern the console uses. A single pass over
|
|
30
|
+
* {@link streamRunEvents}; the deadline is enforced by aborting the stream.
|
|
31
|
+
*
|
|
32
|
+
* The deadline abort is this drain's OWN, expected termination — not a failure.
|
|
33
|
+
* Where it lands decides how {@link streamRunEvents} surfaces it: mid-read it
|
|
34
|
+
* ends the iterator cleanly, but while the underlying `fetch` is still pending —
|
|
35
|
+
* the common case for a poll whose upstream has flushed no bytes yet (no new
|
|
36
|
+
* events after the cursor, so no immediate SSE frame) — it rejects with an
|
|
37
|
+
* `aborted` {@link MaterialGraphError}. Both mean the same thing here, so the
|
|
38
|
+
* self-inflicted abort is swallowed and the collected events returned. Any other
|
|
39
|
+
* failure (a real 4xx/5xx or transport error) still propagates, keeping the
|
|
40
|
+
* fail-fast surface for genuine upstream problems.
|
|
41
|
+
*/
|
|
42
|
+
export declare function drainRunEvents(transport: HttpTransport, runId: string, options: DrainRunEventsOptions): Promise<RunEvent[]>;
|
package/runs.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Run + source-material namespace — the agentic half of the SDK. These
|
|
3
|
+
* functions take the shared {@link HttpTransport} and speak the hosted Run
|
|
4
|
+
* lifecycle: upload source material, list capabilities, create/read/resume/
|
|
5
|
+
* cancel Runs, and stream (or drain) their events. Box's proxy routes are thin
|
|
6
|
+
* wrappers over exactly these calls.
|
|
7
|
+
*/
|
|
8
|
+
import { MaterialGraphError } from "./errors.js";
|
|
9
|
+
import { HttpTransport } from "./http-transport.js";
|
|
10
|
+
import { findSdkOperation, operationPath } from "./operations.js";
|
|
11
|
+
import { readSseFrames } from "./sse.js";
|
|
12
|
+
import { TERMINAL_RUN_EVENT_TYPES, } from "./run-types.js";
|
|
13
|
+
function runOperation(operationId) {
|
|
14
|
+
const match = findSdkOperation(operationId);
|
|
15
|
+
if (!match)
|
|
16
|
+
throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
|
|
17
|
+
return match;
|
|
18
|
+
}
|
|
19
|
+
/** Build a `Blob` from raw bytes, normalising to an ArrayBuffer-backed view. */
|
|
20
|
+
function bytesToBlob(input) {
|
|
21
|
+
const type = input.contentType ? { type: input.contentType } : {};
|
|
22
|
+
if (input.data instanceof ArrayBuffer)
|
|
23
|
+
return new Blob([input.data], type);
|
|
24
|
+
// A Uint8Array may be SharedArrayBuffer-backed; copy into a fresh view so the
|
|
25
|
+
// Blob part is unambiguously ArrayBuffer-backed (no cast needed).
|
|
26
|
+
const copy = new Uint8Array(input.data.byteLength);
|
|
27
|
+
copy.set(input.data);
|
|
28
|
+
return new Blob([copy], type);
|
|
29
|
+
}
|
|
30
|
+
/** Append one file to the multipart body, from either input form. */
|
|
31
|
+
function appendFile(form, field, input) {
|
|
32
|
+
if (input instanceof Blob) {
|
|
33
|
+
if (input instanceof File)
|
|
34
|
+
form.append(field, input, input.name);
|
|
35
|
+
else
|
|
36
|
+
form.append(field, input);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
form.append(field, bytesToBlob(input), input.filename);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Upload one atomic source-material batch (`POST /api/v1/source-material`).
|
|
43
|
+
* Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
|
|
44
|
+
* the legacy `{ brief, references }` shape (→ `brief` + repeatable `references`
|
|
45
|
+
* fields). Both wire to multipart the server accepts.
|
|
46
|
+
*/
|
|
47
|
+
export async function uploadSourceMaterial(transport, input, signal) {
|
|
48
|
+
const form = new FormData();
|
|
49
|
+
if ("files" in input) {
|
|
50
|
+
for (const file of input.files)
|
|
51
|
+
appendFile(form, "files", file);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
appendFile(form, "brief", input.brief);
|
|
55
|
+
for (const reference of input.references ?? [])
|
|
56
|
+
appendFile(form, "references", reference);
|
|
57
|
+
}
|
|
58
|
+
const url = transport.buildUrl(operationPath(runOperation("upload_source_material")));
|
|
59
|
+
// No Content-Type header: fetch sets multipart/form-data with the boundary.
|
|
60
|
+
const { parsed } = await transport.send({
|
|
61
|
+
method: "POST",
|
|
62
|
+
url,
|
|
63
|
+
headers: transport.authHeaders(),
|
|
64
|
+
body: form,
|
|
65
|
+
signal,
|
|
66
|
+
});
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
/** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
|
|
70
|
+
export function capabilities(transport, signal) {
|
|
71
|
+
return transport.requestJson(runOperation("list_public_capabilities"), {
|
|
72
|
+
signal,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Start an asynchronous capability Run (`POST /api/v1/runs`). */
|
|
76
|
+
export function createRun(transport, body, signal) {
|
|
77
|
+
return transport.requestJson(runOperation("create_public_run"), {
|
|
78
|
+
body,
|
|
79
|
+
signal,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
|
|
83
|
+
export function getRun(transport, runId, signal) {
|
|
84
|
+
return transport.requestJson(runOperation("get_public_run"), {
|
|
85
|
+
pathParams: { runId },
|
|
86
|
+
signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`). */
|
|
90
|
+
export function resumeRun(transport, runId, resumeData, signal) {
|
|
91
|
+
return transport.requestJson(runOperation("resume_public_run"), {
|
|
92
|
+
body: { resumeData },
|
|
93
|
+
pathParams: { runId },
|
|
94
|
+
signal,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Request durable Run cancellation (`POST /api/v1/runs/{runId}/cancel`). */
|
|
98
|
+
export function cancelRun(transport, runId, signal) {
|
|
99
|
+
return transport.requestJson(runOperation("cancel_public_run"), {
|
|
100
|
+
body: {},
|
|
101
|
+
pathParams: { runId },
|
|
102
|
+
signal,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** Decode one SSE `data:` frame into a {@link RunEvent}, or `null` when unusable. */
|
|
106
|
+
function decodeRunEvent(data) {
|
|
107
|
+
if (data.length === 0)
|
|
108
|
+
return null;
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(data);
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
throw MaterialGraphError.unexpected(`MaterialGraph run-event stream sent an unparseable data frame: ${String(cause)}`);
|
|
115
|
+
}
|
|
116
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
117
|
+
return null;
|
|
118
|
+
const record = parsed;
|
|
119
|
+
if (typeof record.sequence !== "number" || typeof record.type !== "string")
|
|
120
|
+
return null;
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Stream ordered Run events as an async iterator (`GET
|
|
125
|
+
* /api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or
|
|
126
|
+
* `signal` aborts. One connection is bounded server-side (~25s or a terminal
|
|
127
|
+
* event); pass `after` to resume from the last observed sequence.
|
|
128
|
+
*/
|
|
129
|
+
export async function* streamRunEvents(transport, runId, options = {}) {
|
|
130
|
+
const operation = runOperation("list_public_run_events");
|
|
131
|
+
const url = transport.buildUrl(operationPath(operation, { runId }), options.after === undefined ? undefined : { after: options.after });
|
|
132
|
+
const requestUrl = new URL(url);
|
|
133
|
+
const metadata = { method: "GET", origin: requestUrl.origin, path: requestUrl.pathname };
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await transport.fetch(url, {
|
|
137
|
+
method: "GET",
|
|
138
|
+
headers: transport.authHeaders({ Accept: "text/event-stream" }),
|
|
139
|
+
signal: options.signal,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (cause) {
|
|
143
|
+
throw MaterialGraphError.fromTransport({ ...metadata, cause, callerSignal: options.signal });
|
|
144
|
+
}
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const text = await response.text().catch(() => "");
|
|
147
|
+
let parsed;
|
|
148
|
+
if (text.length > 0) {
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(text);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
parsed = text;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw MaterialGraphError.fromResponse({
|
|
157
|
+
...metadata,
|
|
158
|
+
status: response.status,
|
|
159
|
+
body: parsed,
|
|
160
|
+
responseText: text,
|
|
161
|
+
rateLimit: transport.lastRateLimit,
|
|
162
|
+
apiKey: transport.apiKey,
|
|
163
|
+
queryValues: [...requestUrl.searchParams.values()],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (response.body === null) {
|
|
167
|
+
throw MaterialGraphError.unexpected("MaterialGraph run-event stream returned no body");
|
|
168
|
+
}
|
|
169
|
+
for await (const frame of readSseFrames(response.body, options.signal)) {
|
|
170
|
+
const event = decodeRunEvent(frame.data);
|
|
171
|
+
if (event)
|
|
172
|
+
yield event;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Collect Run events until a terminal event, the deadline, or the stream ends —
|
|
177
|
+
* the bounded events-log pattern the console uses. A single pass over
|
|
178
|
+
* {@link streamRunEvents}; the deadline is enforced by aborting the stream.
|
|
179
|
+
*
|
|
180
|
+
* The deadline abort is this drain's OWN, expected termination — not a failure.
|
|
181
|
+
* Where it lands decides how {@link streamRunEvents} surfaces it: mid-read it
|
|
182
|
+
* ends the iterator cleanly, but while the underlying `fetch` is still pending —
|
|
183
|
+
* the common case for a poll whose upstream has flushed no bytes yet (no new
|
|
184
|
+
* events after the cursor, so no immediate SSE frame) — it rejects with an
|
|
185
|
+
* `aborted` {@link MaterialGraphError}. Both mean the same thing here, so the
|
|
186
|
+
* self-inflicted abort is swallowed and the collected events returned. Any other
|
|
187
|
+
* failure (a real 4xx/5xx or transport error) still propagates, keeping the
|
|
188
|
+
* fail-fast surface for genuine upstream problems.
|
|
189
|
+
*/
|
|
190
|
+
export async function drainRunEvents(transport, runId, options) {
|
|
191
|
+
const collected = [];
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const timer = setTimeout(() => controller.abort(), options.deadlineMs);
|
|
194
|
+
try {
|
|
195
|
+
for await (const event of streamRunEvents(transport, runId, {
|
|
196
|
+
after: options.after,
|
|
197
|
+
signal: controller.signal,
|
|
198
|
+
})) {
|
|
199
|
+
collected.push(event);
|
|
200
|
+
if (TERMINAL_RUN_EVENT_TYPES.has(event.type))
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const causedByDeadline = controller.signal.aborted && error instanceof MaterialGraphError && error.kind === "aborted";
|
|
206
|
+
if (!causedByDeadline)
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
controller.abort();
|
|
212
|
+
}
|
|
213
|
+
return collected;
|
|
214
|
+
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/** Types for structured refinements returned by MaterialGraph search. */
|
|
2
2
|
import type { SearchInput } from "./types.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* A hard canonical-attribute filter accepted by the search engine — either a
|
|
5
|
+
* term match (`{ attributeId, term }`) or a presence check
|
|
6
|
+
* (`{ attributeId, hasValue: true }`). Applied as a restriction BEFORE ranking.
|
|
7
|
+
*
|
|
8
|
+
* The `attributeId` may be a real registry id (`"fire_classifications"`,
|
|
9
|
+
* `"environmental_product_declaration"`) OR the virtual
|
|
10
|
+
* `"certification_programme"` (see `CERTIFICATION_PROGRAMME_ATTRIBUTE_ID`),
|
|
11
|
+
* which matches a programme — GREENGUARD, FloorScore, LEED, an EPD — wherever it
|
|
12
|
+
* landed across the four certification buckets. Presence (`hasValue`) is NOT
|
|
13
|
+
* supported for the virtual programme attribute. The typed `spec-types` mirror
|
|
14
|
+
* and the `specifications` builders produce values assignable to this type.
|
|
15
|
+
*/
|
|
4
16
|
export type SearchFilter = {
|
|
5
17
|
attributeId: string;
|
|
6
18
|
term: string;
|
package/spec-types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The specification surface — the typed vocabulary for asking a material
|
|
3
|
+
* "does it meet this spec?": certifications (GREENGUARD, FloorScore, Declare),
|
|
4
|
+
* VOC / emissions testing (CDPH v1.2), sustainability programmes (LEED, EPDs),
|
|
5
|
+
* and fire / abrasion / slip classifications.
|
|
6
|
+
*
|
|
7
|
+
* Every shape here is a hand-authored, zero-dependency mirror of the server's
|
|
8
|
+
* spec filter contract (`specFilterSchema` in
|
|
9
|
+
* `@materialgraph/products/search/schema`). It exists so a caller answering a
|
|
10
|
+
* question like "find me a GREENGUARD-certified alternative" or "the same fire
|
|
11
|
+
* class, but with an EPD on record" reaches for a NAMED, typed criterion instead
|
|
12
|
+
* of hand-assembling a magic `attribute:value` string.
|
|
13
|
+
*
|
|
14
|
+
* These criteria drive two operations equally:
|
|
15
|
+
* - `client.search({ filters: [...] })` — spec gates layered onto a ranked
|
|
16
|
+
* (colour / visual / lexical) search.
|
|
17
|
+
* - `client.bySpec(...)` — the value-anchored, spec-only primitive, which takes
|
|
18
|
+
* the same criteria as `attribute:value` strings (build them with the helpers
|
|
19
|
+
* in `./specifications`).
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
|
23
|
+
* landed at ingest.
|
|
24
|
+
*
|
|
25
|
+
* Certifications are not stored under one attribute: each record is an object
|
|
26
|
+
* (`{ programme, name, status, … }`) split across FOUR real object-array
|
|
27
|
+
* attributes by intent — general `certifications`,
|
|
28
|
+
* `sustainability_certifications`, `material_health_certifications`, and
|
|
29
|
+
* `voc_emissions_testing_certificate` (GREENGUARD Gold / FloorScore / CDPH land
|
|
30
|
+
* in the last). A caller shouldn't have to know which bucket a programme fell
|
|
31
|
+
* into, so this single virtual id matches the `programme` term across all four.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // "find a GREENGUARD-certified alternative"
|
|
35
|
+
* client.search({
|
|
36
|
+
* similarTo: { variantId: seed },
|
|
37
|
+
* filters: [{ attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, term: "GREENGUARD Gold" }],
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
export declare const CERTIFICATION_PROGRAMME_ATTRIBUTE_ID = "certification_programme";
|
|
41
|
+
/** The literal type of {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. */
|
|
42
|
+
export type CertificationProgrammeAttributeId = typeof CERTIFICATION_PROGRAMME_ATTRIBUTE_ID;
|
|
43
|
+
/**
|
|
44
|
+
* A term-equality spec criterion: the attribute must carry this canonical value,
|
|
45
|
+
* e.g. `{ attributeId: "fire_classifications", term: "astm_e84_class_a" }`.
|
|
46
|
+
* Discover valid `attributeId`/`term` pairs via `client.specificationFacets()`
|
|
47
|
+
* and `client.attributeValues()`, or resolve free text with
|
|
48
|
+
* `client.resolveAttribute()`.
|
|
49
|
+
*/
|
|
50
|
+
export interface SpecTermFilter {
|
|
51
|
+
attributeId: string;
|
|
52
|
+
term: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A presence spec criterion: the attribute must have ANY accepted value —
|
|
56
|
+
* "has an EPD on record", "has a measured slip figure at all". Not supported for
|
|
57
|
+
* {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID} (a programme is matched by name,
|
|
58
|
+
* never by bare presence).
|
|
59
|
+
*/
|
|
60
|
+
export interface SpecPresenceFilter {
|
|
61
|
+
attributeId: string;
|
|
62
|
+
hasValue: true;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A certification-programme criterion — a {@link SpecTermFilter} pinned to the
|
|
66
|
+
* virtual {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. Structurally assignable
|
|
67
|
+
* to {@link SpecFilter}, so it drops straight into `search({ filters })`.
|
|
68
|
+
*/
|
|
69
|
+
export interface CertificationProgrammeFilter {
|
|
70
|
+
attributeId: CertificationProgrammeAttributeId;
|
|
71
|
+
term: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A hard, canonical-attribute spec criterion — either a term match or a
|
|
75
|
+
* presence check. The union the search engine applies as a restriction BEFORE
|
|
76
|
+
* ranking. Identical in shape to the SDK's `SearchFilter`.
|
|
77
|
+
*/
|
|
78
|
+
export type SpecFilter = SpecTermFilter | SpecPresenceFilter;
|