@instruments/materialgraph 0.1.0 → 0.3.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/by-spec-types.d.ts +73 -0
- package/by-spec-types.js +7 -0
- package/client.d.ts +186 -7
- package/client.js +242 -125
- package/codes-client.d.ts +82 -0
- package/codes-client.js +73 -0
- package/codes-types.d.ts +238 -0
- package/codes-types.js +1 -0
- package/colour-aggregate-types.d.ts +93 -0
- package/colour-aggregate-types.js +1 -0
- package/colours-client.d.ts +52 -0
- package/colours-client.js +39 -0
- package/composition-types.d.ts +343 -0
- package/composition-types.js +10 -0
- package/http-transport.d.ts +74 -0
- package/http-transport.js +201 -0
- package/index.d.ts +19 -0
- package/index.js +9 -0
- package/match-stream-types.d.ts +150 -0
- package/match-stream-types.js +28 -0
- package/match-stream.d.ts +22 -0
- package/match-stream.js +280 -0
- package/material-block-types.d.ts +23 -2
- package/material-types.d.ts +92 -0
- package/material-types.js +1 -0
- package/ndjson.d.ts +46 -0
- package/ndjson.js +103 -0
- package/operations.d.ts +6 -2
- package/operations.js +30 -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 +266 -0
- package/run-types.js +6 -0
- package/runs.d.ts +84 -0
- package/runs.js +253 -0
- package/schedule-types.d.ts +88 -0
- package/schedule-types.js +12 -0
- package/search-refinement-types.d.ts +26 -1
- package/search-result-validation.d.ts +27 -0
- package/search-result-validation.js +174 -0
- package/source-material-validation.d.ts +3 -0
- package/source-material-validation.js +109 -0
- package/spec-types.d.ts +82 -0
- package/spec-types.js +44 -0
- package/specifications.d.ts +72 -0
- package/specifications.js +82 -0
- package/sse.d.ts +35 -0
- package/sse.js +147 -0
- package/types.d.ts +204 -77
- package/validation.d.ts +119 -0
- package/validation.js +241 -0
package/run-types.d.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
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 { JsonValue, 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
|
+
/**
|
|
78
|
+
* One analysis computed over an uploaded image's bytes.
|
|
79
|
+
*
|
|
80
|
+
* Analyses are CONTENT-ADDRESSED: keyed by the file's SHA-256, not by the
|
|
81
|
+
* upload or the uploading key. Re-uploading an image someone has already
|
|
82
|
+
* analysed therefore returns that analysis immediately, with no work. That
|
|
83
|
+
* sharing is by design and discloses nothing — you only ever see analyses of
|
|
84
|
+
* images you hold yourself.
|
|
85
|
+
*/
|
|
86
|
+
export interface SourceImageAnalysis {
|
|
87
|
+
/** The kind of read. {@link OpenEnum} keeps a newer server value type-checking. */
|
|
88
|
+
kind: OpenEnum<"scene_match">;
|
|
89
|
+
/** The model version that produced {@link SourceImageAnalysis.result}. */
|
|
90
|
+
model: string;
|
|
91
|
+
/**
|
|
92
|
+
* `failed` analyses are RETURNED, not hidden — so "we looked and it did not
|
|
93
|
+
* work" is distinguishable from "nobody has looked yet", which is an empty
|
|
94
|
+
* {@link SourceMaterialBatchFile.analyses}.
|
|
95
|
+
*/
|
|
96
|
+
status: OpenEnum<"ok" | "failed">;
|
|
97
|
+
/**
|
|
98
|
+
* The payload verbatim, or the failure record when `status` is `failed`. For
|
|
99
|
+
* `scene_match`: the detected regions with their candidate variant ids and
|
|
100
|
+
* scores. The interior shape belongs to the producing model and may gain
|
|
101
|
+
* fields, so it is typed as JSON rather than pinned here.
|
|
102
|
+
*/
|
|
103
|
+
result: Record<string, JsonValue>;
|
|
104
|
+
/** ISO-8601 timestamp of the most recent analysis. */
|
|
105
|
+
analysedAt: string;
|
|
106
|
+
}
|
|
107
|
+
/** One file as `GET /api/v1/source-material/{batchId}` returns it. */
|
|
108
|
+
export interface SourceMaterialBatchFile extends SourceMaterialReceiptFile {
|
|
109
|
+
/** Fetchable URL for the stored bytes. */
|
|
110
|
+
url: string;
|
|
111
|
+
/**
|
|
112
|
+
* ALWAYS an array. `[]` means nothing has been computed yet — never a missing
|
|
113
|
+
* field. Preprocessing is triggered manually today; when it becomes automatic
|
|
114
|
+
* only the trigger changes and this contract does not.
|
|
115
|
+
*/
|
|
116
|
+
analyses: SourceImageAnalysis[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The `200` body of `GET /api/v1/source-material/{batchId}` — the batch, every
|
|
120
|
+
* file it holds, and the analyses computed over each file's bytes.
|
|
121
|
+
*/
|
|
122
|
+
export interface SourceMaterialBatchDetail {
|
|
123
|
+
/** The stored batch id (`smb_…`). */
|
|
124
|
+
batchId: string;
|
|
125
|
+
/** ISO-8601 timestamp of the upload. */
|
|
126
|
+
createdAt: string;
|
|
127
|
+
sources: SourceMaterialBatchFile[];
|
|
128
|
+
}
|
|
129
|
+
export type CapabilityKind = OpenEnum<"agent" | "tool" | "workflow">;
|
|
130
|
+
export type CapabilityExposure = OpenEnum<"public" | "partner" | "internal">;
|
|
131
|
+
export type CapabilityScope = OpenEnum<"mg:read" | "mg:write" | "mg:ingest" | "mg:admin">;
|
|
132
|
+
export type CapabilitySideEffectClass = OpenEnum<"read_only" | "browser_local" | "external_read" | "writes_proposed" | "writes_persisted" | "planned_only">;
|
|
133
|
+
export interface CapabilityRetryPolicy {
|
|
134
|
+
maxAttempts: number;
|
|
135
|
+
backoff: OpenEnum<"none" | "fixed" | "exponential">;
|
|
136
|
+
}
|
|
137
|
+
export interface CapabilityInvokeLink {
|
|
138
|
+
href: string;
|
|
139
|
+
method: OpenEnum<"GET" | "POST">;
|
|
140
|
+
}
|
|
141
|
+
/** One invocable capability from the governed public manifest. */
|
|
142
|
+
export interface PublicCapabilityMetadata {
|
|
143
|
+
publicId: string;
|
|
144
|
+
runtimeId: string;
|
|
145
|
+
kind: CapabilityKind;
|
|
146
|
+
exposure: CapabilityExposure;
|
|
147
|
+
name: string;
|
|
148
|
+
description: string;
|
|
149
|
+
schemas: {
|
|
150
|
+
input: Record<string, unknown>;
|
|
151
|
+
output: Record<string, unknown>;
|
|
152
|
+
};
|
|
153
|
+
sideEffectClass: CapabilitySideEffectClass;
|
|
154
|
+
scopes: CapabilityScope[];
|
|
155
|
+
timeoutMs: number;
|
|
156
|
+
retryPolicy: CapabilityRetryPolicy;
|
|
157
|
+
tags: string[];
|
|
158
|
+
links: {
|
|
159
|
+
invoke: CapabilityInvokeLink;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** The `GET /api/v1/capabilities` response envelope. */
|
|
163
|
+
export interface CapabilityManifestResponse {
|
|
164
|
+
capabilities: PublicCapabilityMetadata[];
|
|
165
|
+
}
|
|
166
|
+
export type PublicRunStatus = OpenEnum<"queued" | "claimed" | "running" | "suspended" | "succeeded" | "failed" | "canceled">;
|
|
167
|
+
export interface PublicRunFailure {
|
|
168
|
+
code: string;
|
|
169
|
+
message: string;
|
|
170
|
+
retryable: boolean;
|
|
171
|
+
detail?: Record<string, unknown>;
|
|
172
|
+
}
|
|
173
|
+
export interface PublicRunArtifactRef {
|
|
174
|
+
kind: string;
|
|
175
|
+
ref: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The caller-owned, redacted Run ledger record. Prompts, raw model messages,
|
|
179
|
+
* reasoning, credentials, provider payloads, and source-document contents are
|
|
180
|
+
* never returned — inputs and outputs are the `redacted*Json` projections.
|
|
181
|
+
*/
|
|
182
|
+
export interface PublicRun {
|
|
183
|
+
id: string;
|
|
184
|
+
capabilityKind: CapabilityKind;
|
|
185
|
+
capabilityId: string;
|
|
186
|
+
clientReference: string | null;
|
|
187
|
+
status: PublicRunStatus;
|
|
188
|
+
redactedInputJson: Record<string, unknown>;
|
|
189
|
+
redactedOutputJson: Record<string, unknown> | null;
|
|
190
|
+
usageJson: Record<string, unknown> | null;
|
|
191
|
+
artifactRefs: PublicRunArtifactRef[] | null;
|
|
192
|
+
failureJson: PublicRunFailure | null;
|
|
193
|
+
suspensionStepId: string | null;
|
|
194
|
+
eventCursor: number;
|
|
195
|
+
attempts: number;
|
|
196
|
+
startedAt: string | null;
|
|
197
|
+
cancellationRequestedAt: string | null;
|
|
198
|
+
completedAt: string | null;
|
|
199
|
+
createdAt: string;
|
|
200
|
+
updatedAt: string;
|
|
201
|
+
}
|
|
202
|
+
/** Body for `POST /api/v1/runs` ({@link MaterialGraphClient.createRun}). */
|
|
203
|
+
export interface CreatePublicRunRequest {
|
|
204
|
+
capabilityKind: CapabilityKind;
|
|
205
|
+
capabilityId: string;
|
|
206
|
+
/** The capability input, validated server-side against its live input schema. */
|
|
207
|
+
inputJson: unknown;
|
|
208
|
+
/** Stable caller key making Run creation idempotent across retries. */
|
|
209
|
+
idempotencyKey?: string;
|
|
210
|
+
/** Free-form caller correlation reference, echoed back on the Run. */
|
|
211
|
+
clientReference?: string;
|
|
212
|
+
}
|
|
213
|
+
/** The `POST /api/v1/runs` response (`200` replay or `202` accepted). */
|
|
214
|
+
export interface CreatePublicRunResponse {
|
|
215
|
+
accepted: boolean;
|
|
216
|
+
replayed: boolean;
|
|
217
|
+
workflowRunId: string;
|
|
218
|
+
run: PublicRun;
|
|
219
|
+
}
|
|
220
|
+
/** The `GET /api/v1/runs/{runId}` and resume/cancel response envelope. */
|
|
221
|
+
export interface PublicRunResponse {
|
|
222
|
+
run: PublicRun;
|
|
223
|
+
}
|
|
224
|
+
/** Body for `POST /api/v1/runs/{runId}/resume`. */
|
|
225
|
+
export interface ResumePublicRunRequest {
|
|
226
|
+
/** The resume payload, validated against the suspended step's resume schema. */
|
|
227
|
+
resumeData: unknown;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* One redacted Run event, decoded from an SSE `data:` frame. The stream yields
|
|
231
|
+
* these in ascending {@link RunEvent.sequence} order. `payloadJson` is read
|
|
232
|
+
* structurally; use {@link parseRunEvent} to narrow the composition family.
|
|
233
|
+
*/
|
|
234
|
+
export interface RunEvent {
|
|
235
|
+
sequence: number;
|
|
236
|
+
type: string;
|
|
237
|
+
payloadJson: unknown;
|
|
238
|
+
}
|
|
239
|
+
/** Run event types that close the stream — a terminal event ends a drain. */
|
|
240
|
+
export declare const TERMINAL_RUN_EVENT_TYPES: ReadonlySet<string>;
|
|
241
|
+
/** Options for {@link MaterialGraphClient.streamRunEvents}. */
|
|
242
|
+
export interface StreamRunEventsOptions {
|
|
243
|
+
/** Resume the stream after this sequence (exclusive). Omitted starts at 0. */
|
|
244
|
+
after?: number;
|
|
245
|
+
/** Abort the stream; iteration stops cleanly. */
|
|
246
|
+
signal?: AbortSignal;
|
|
247
|
+
}
|
|
248
|
+
/** Options for {@link MaterialGraphClient.drainRunEvents}. */
|
|
249
|
+
export interface DrainRunEventsOptions {
|
|
250
|
+
/** Resume after this sequence (exclusive). */
|
|
251
|
+
after?: number;
|
|
252
|
+
/** Stop collecting after this many milliseconds even without a terminal event. */
|
|
253
|
+
deadlineMs: number;
|
|
254
|
+
}
|
|
255
|
+
/** Options for {@link MaterialGraphClient.resumeRun}. */
|
|
256
|
+
export interface ResumeRunOptions {
|
|
257
|
+
/**
|
|
258
|
+
* The step the caller believes is suspended. When it mismatches the run
|
|
259
|
+
* doc's live `suspensionStepId` the API answers RFC-7807 409 (MG-608) —
|
|
260
|
+
* which is what stops a stale client resuming a step the run has already
|
|
261
|
+
* moved past. Omitted, the resume is unguarded, exactly as before.
|
|
262
|
+
*/
|
|
263
|
+
stepId?: string;
|
|
264
|
+
/** Abort the request. */
|
|
265
|
+
signal?: AbortSignal;
|
|
266
|
+
}
|
package/run-types.js
ADDED
package/runs.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { HttpTransport } from "./http-transport.js";
|
|
2
|
+
import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
|
|
3
|
+
import { type CapabilityManifestResponse, type CreatePublicRunRequest, type CreatePublicRunResponse, type DrainRunEventsOptions, type PublicRunResponse, type ResumeRunOptions, type RunEvent, type SourceMaterialBatchDetail, type SourceMaterialBatchReceipt, type StreamRunEventsOptions, type UploadSourceMaterialInput } from "./run-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Upload one atomic source-material batch (`POST /api/v1/source-material`).
|
|
6
|
+
* Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
|
|
7
|
+
* the legacy `{ brief, references }` shape (→ `brief` + repeatable `references`
|
|
8
|
+
* fields). Both wire to multipart the server accepts.
|
|
9
|
+
*/
|
|
10
|
+
export declare function uploadSourceMaterial(transport: HttpTransport, input: UploadSourceMaterialInput, signal?: AbortSignal): Promise<SourceMaterialBatchReceipt>;
|
|
11
|
+
/**
|
|
12
|
+
* Read back one uploaded batch (`GET /api/v1/source-material/{batchId}`): every
|
|
13
|
+
* file with a fetchable `url`, and every analysis computed over its bytes
|
|
14
|
+
* inlined as `analyses` — always an array, `[]` when nothing has been computed.
|
|
15
|
+
*
|
|
16
|
+
* A batch you do not own is indistinguishable from one that does not exist:
|
|
17
|
+
* both raise a 404, never a 403, so batch ids cannot be probed for existence.
|
|
18
|
+
*
|
|
19
|
+
* The body is VALIDATED, not trusted: `checkSourceMaterialBatchDetail` mirrors
|
|
20
|
+
* the server's Zod contract so a malformed response fails here with a path into
|
|
21
|
+
* the payload rather than as an undefined three frames away in caller code.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSourceMaterialBatch(transport: HttpTransport, batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
|
|
24
|
+
/** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
|
|
25
|
+
export declare function capabilities(transport: HttpTransport, signal?: AbortSignal): Promise<CapabilityManifestResponse>;
|
|
26
|
+
/** Start an asynchronous capability Run (`POST /api/v1/runs`). */
|
|
27
|
+
export declare function createRun(transport: HttpTransport, body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
|
|
28
|
+
/** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
|
|
29
|
+
export declare function getRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
30
|
+
/**
|
|
31
|
+
* Export a completed compose Run's scheme as a finish schedule — one row per
|
|
32
|
+
* slot, every attribute value carrying its source and verification state
|
|
33
|
+
* (`GET /api/v1/runs/{runId}/schedule`).
|
|
34
|
+
*
|
|
35
|
+
* The `.xlsx` form of the same export is deliberately NOT an SDK method: this
|
|
36
|
+
* transport parses every response as text/JSON and has no binary read path, so
|
|
37
|
+
* a bytes-returning method would mean a second transport surface for one file.
|
|
38
|
+
* Fetch `GET /api/v1/runs/{runId}/schedule.xlsx` directly with the same bearer
|
|
39
|
+
* key when you want the workbook.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getRunSchedule(transport: HttpTransport, runId: string, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`).
|
|
44
|
+
*
|
|
45
|
+
* The third argument takes either an `AbortSignal` (legacy) or
|
|
46
|
+
* {@link ResumeRunOptions}.
|
|
47
|
+
*
|
|
48
|
+
* `stepId` is forwarded whenever it is PRESENT — `!== undefined`, not truthy.
|
|
49
|
+
* An empty string is a present-but-malformed value and must reach the server
|
|
50
|
+
* so its schema rejects it; treating it as absent would silently downgrade a
|
|
51
|
+
* guarded resume into an unguarded one, which is the exact failure this guard
|
|
52
|
+
* exists to prevent.
|
|
53
|
+
*
|
|
54
|
+
* Omitting it sends a body byte-identical to what this sent before. Note the
|
|
55
|
+
* wire cannot distinguish `{ stepId: undefined }` from no key at all —
|
|
56
|
+
* `JSON.stringify` drops both — so the distinction here is construction
|
|
57
|
+
* intent, kept so the serialised body is provably unchanged for old callers.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resumeRun(transport: HttpTransport, runId: string, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
|
|
60
|
+
/** Request durable Run cancellation (`POST /api/v1/runs/{runId}/cancel`). */
|
|
61
|
+
export declare function cancelRun(transport: HttpTransport, runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
|
|
62
|
+
/**
|
|
63
|
+
* Stream ordered Run events as an async iterator (`GET
|
|
64
|
+
* /api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or
|
|
65
|
+
* `signal` aborts. One connection is bounded server-side (~25s or a terminal
|
|
66
|
+
* event); pass `after` to resume from the last observed sequence.
|
|
67
|
+
*/
|
|
68
|
+
export declare function streamRunEvents(transport: HttpTransport, runId: string, options?: StreamRunEventsOptions): AsyncGenerator<RunEvent, void, unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* Collect Run events until a terminal event, the deadline, or the stream ends —
|
|
71
|
+
* the bounded events-log pattern the console uses. A single pass over
|
|
72
|
+
* {@link streamRunEvents}; the deadline is enforced by aborting the stream.
|
|
73
|
+
*
|
|
74
|
+
* The deadline abort is this drain's OWN, expected termination — not a failure.
|
|
75
|
+
* Where it lands decides how {@link streamRunEvents} surfaces it: mid-read it
|
|
76
|
+
* ends the iterator cleanly, but while the underlying `fetch` is still pending —
|
|
77
|
+
* the common case for a poll whose upstream has flushed no bytes yet (no new
|
|
78
|
+
* events after the cursor, so no immediate SSE frame) — it rejects with an
|
|
79
|
+
* `aborted` {@link MaterialGraphError}. Both mean the same thing here, so the
|
|
80
|
+
* self-inflicted abort is swallowed and the collected events returned. Any other
|
|
81
|
+
* failure (a real 4xx/5xx or transport error) still propagates, keeping the
|
|
82
|
+
* fail-fast surface for genuine upstream problems.
|
|
83
|
+
*/
|
|
84
|
+
export declare function drainRunEvents(transport: HttpTransport, runId: string, options: DrainRunEventsOptions): Promise<RunEvent[]>;
|
package/runs.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
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 { checkSourceMaterialBatchDetail } from "./source-material-validation.js";
|
|
12
|
+
import { readSseFrames } from "./sse.js";
|
|
13
|
+
import { TERMINAL_RUN_EVENT_TYPES, } from "./run-types.js";
|
|
14
|
+
function runOperation(operationId) {
|
|
15
|
+
const match = findSdkOperation(operationId);
|
|
16
|
+
if (!match)
|
|
17
|
+
throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
|
|
18
|
+
return match;
|
|
19
|
+
}
|
|
20
|
+
/** Build a `Blob` from raw bytes, normalising to an ArrayBuffer-backed view. */
|
|
21
|
+
function bytesToBlob(input) {
|
|
22
|
+
const type = input.contentType ? { type: input.contentType } : {};
|
|
23
|
+
if (input.data instanceof ArrayBuffer)
|
|
24
|
+
return new Blob([input.data], type);
|
|
25
|
+
// A Uint8Array may be SharedArrayBuffer-backed; copy into a fresh view so the
|
|
26
|
+
// Blob part is unambiguously ArrayBuffer-backed (no cast needed).
|
|
27
|
+
const copy = new Uint8Array(input.data.byteLength);
|
|
28
|
+
copy.set(input.data);
|
|
29
|
+
return new Blob([copy], type);
|
|
30
|
+
}
|
|
31
|
+
/** Append one file to the multipart body, from either input form. */
|
|
32
|
+
function appendFile(form, field, input) {
|
|
33
|
+
if (input instanceof Blob) {
|
|
34
|
+
if (input instanceof File)
|
|
35
|
+
form.append(field, input, input.name);
|
|
36
|
+
else
|
|
37
|
+
form.append(field, input);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
form.append(field, bytesToBlob(input), input.filename);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Upload one atomic source-material batch (`POST /api/v1/source-material`).
|
|
44
|
+
* Accepts the pile-of-files shape (`{ files }` → repeatable `files` field) or
|
|
45
|
+
* the legacy `{ brief, references }` shape (→ `brief` + repeatable `references`
|
|
46
|
+
* fields). Both wire to multipart the server accepts.
|
|
47
|
+
*/
|
|
48
|
+
export async function uploadSourceMaterial(transport, input, signal) {
|
|
49
|
+
const form = new FormData();
|
|
50
|
+
if ("files" in input) {
|
|
51
|
+
for (const file of input.files)
|
|
52
|
+
appendFile(form, "files", file);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
appendFile(form, "brief", input.brief);
|
|
56
|
+
for (const reference of input.references ?? [])
|
|
57
|
+
appendFile(form, "references", reference);
|
|
58
|
+
}
|
|
59
|
+
const url = transport.buildUrl(operationPath(runOperation("upload_source_material")));
|
|
60
|
+
// No Content-Type header: fetch sets multipart/form-data with the boundary.
|
|
61
|
+
const { parsed } = await transport.send({
|
|
62
|
+
method: "POST",
|
|
63
|
+
url,
|
|
64
|
+
headers: transport.authHeaders(),
|
|
65
|
+
body: form,
|
|
66
|
+
signal,
|
|
67
|
+
});
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read back one uploaded batch (`GET /api/v1/source-material/{batchId}`): every
|
|
72
|
+
* file with a fetchable `url`, and every analysis computed over its bytes
|
|
73
|
+
* inlined as `analyses` — always an array, `[]` when nothing has been computed.
|
|
74
|
+
*
|
|
75
|
+
* A batch you do not own is indistinguishable from one that does not exist:
|
|
76
|
+
* both raise a 404, never a 403, so batch ids cannot be probed for existence.
|
|
77
|
+
*
|
|
78
|
+
* The body is VALIDATED, not trusted: `checkSourceMaterialBatchDetail` mirrors
|
|
79
|
+
* the server's Zod contract so a malformed response fails here with a path into
|
|
80
|
+
* the payload rather than as an undefined three frames away in caller code.
|
|
81
|
+
*/
|
|
82
|
+
export async function getSourceMaterialBatch(transport, batchId, signal) {
|
|
83
|
+
const body = await transport.requestJson(runOperation("get_source_material_batch"), {
|
|
84
|
+
pathParams: { batchId },
|
|
85
|
+
signal,
|
|
86
|
+
});
|
|
87
|
+
return checkSourceMaterialBatchDetail(body, batchId);
|
|
88
|
+
}
|
|
89
|
+
/** List invocable agents, workflows, and tools (`GET /api/v1/capabilities`). */
|
|
90
|
+
export function capabilities(transport, signal) {
|
|
91
|
+
return transport.requestJson(runOperation("list_public_capabilities"), {
|
|
92
|
+
signal,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Start an asynchronous capability Run (`POST /api/v1/runs`). */
|
|
96
|
+
export function createRun(transport, body, signal) {
|
|
97
|
+
return transport.requestJson(runOperation("create_public_run"), {
|
|
98
|
+
body,
|
|
99
|
+
signal,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Get a Run's current state (`GET /api/v1/runs/{runId}`). */
|
|
103
|
+
export function getRun(transport, runId, signal) {
|
|
104
|
+
return transport.requestJson(runOperation("get_public_run"), {
|
|
105
|
+
pathParams: { runId },
|
|
106
|
+
signal,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Export a completed compose Run's scheme as a finish schedule — one row per
|
|
111
|
+
* slot, every attribute value carrying its source and verification state
|
|
112
|
+
* (`GET /api/v1/runs/{runId}/schedule`).
|
|
113
|
+
*
|
|
114
|
+
* The `.xlsx` form of the same export is deliberately NOT an SDK method: this
|
|
115
|
+
* transport parses every response as text/JSON and has no binary read path, so
|
|
116
|
+
* a bytes-returning method would mean a second transport surface for one file.
|
|
117
|
+
* Fetch `GET /api/v1/runs/{runId}/schedule.xlsx` directly with the same bearer
|
|
118
|
+
* key when you want the workbook.
|
|
119
|
+
*/
|
|
120
|
+
export function getRunSchedule(transport, runId, options = {}) {
|
|
121
|
+
return transport.requestJson(runOperation("get_run_finish_schedule"), {
|
|
122
|
+
pathParams: { runId },
|
|
123
|
+
...(options.schemeId ? { query: { schemeId: options.schemeId } } : {}),
|
|
124
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Normalise `resumeRun`'s overloaded third argument.
|
|
129
|
+
*
|
|
130
|
+
* A bare `AbortSignal` stays supported because it is the published shape:
|
|
131
|
+
* reordering that parameter would break any consumer passing it positionally,
|
|
132
|
+
* and this ships with no version bump.
|
|
133
|
+
*
|
|
134
|
+
* Discriminated on `instanceof AbortSignal`, NOT on the presence of `stepId`.
|
|
135
|
+
* `{ signal }` with no `stepId` is a perfectly valid options object, and a
|
|
136
|
+
* key-presence test would misread it as a signal and silently drop the abort.
|
|
137
|
+
*/
|
|
138
|
+
function resumeOptions(value) {
|
|
139
|
+
if (value === undefined)
|
|
140
|
+
return {};
|
|
141
|
+
return value instanceof AbortSignal ? { signal: value } : value;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resume a suspended Run (`POST /api/v1/runs/{runId}/resume`).
|
|
145
|
+
*
|
|
146
|
+
* The third argument takes either an `AbortSignal` (legacy) or
|
|
147
|
+
* {@link ResumeRunOptions}.
|
|
148
|
+
*
|
|
149
|
+
* `stepId` is forwarded whenever it is PRESENT — `!== undefined`, not truthy.
|
|
150
|
+
* An empty string is a present-but-malformed value and must reach the server
|
|
151
|
+
* so its schema rejects it; treating it as absent would silently downgrade a
|
|
152
|
+
* guarded resume into an unguarded one, which is the exact failure this guard
|
|
153
|
+
* exists to prevent.
|
|
154
|
+
*
|
|
155
|
+
* Omitting it sends a body byte-identical to what this sent before. Note the
|
|
156
|
+
* wire cannot distinguish `{ stepId: undefined }` from no key at all —
|
|
157
|
+
* `JSON.stringify` drops both — so the distinction here is construction
|
|
158
|
+
* intent, kept so the serialised body is provably unchanged for old callers.
|
|
159
|
+
*/
|
|
160
|
+
export function resumeRun(transport, runId, resumeData, signalOrOptions) {
|
|
161
|
+
const { stepId, signal } = resumeOptions(signalOrOptions);
|
|
162
|
+
return transport.requestJson(runOperation("resume_public_run"), {
|
|
163
|
+
body: { resumeData, ...(stepId !== undefined ? { stepId } : {}) },
|
|
164
|
+
pathParams: { runId },
|
|
165
|
+
signal,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Request durable Run cancellation (`POST /api/v1/runs/{runId}/cancel`). */
|
|
169
|
+
export function cancelRun(transport, runId, signal) {
|
|
170
|
+
return transport.requestJson(runOperation("cancel_public_run"), {
|
|
171
|
+
body: {},
|
|
172
|
+
pathParams: { runId },
|
|
173
|
+
signal,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Decode one SSE `data:` frame into a {@link RunEvent}, or `null` when unusable. */
|
|
177
|
+
function decodeRunEvent(data) {
|
|
178
|
+
if (data.length === 0)
|
|
179
|
+
return null;
|
|
180
|
+
let parsed;
|
|
181
|
+
try {
|
|
182
|
+
parsed = JSON.parse(data);
|
|
183
|
+
}
|
|
184
|
+
catch (cause) {
|
|
185
|
+
throw MaterialGraphError.unexpected(`MaterialGraph run-event stream sent an unparseable data frame: ${String(cause)}`);
|
|
186
|
+
}
|
|
187
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
188
|
+
return null;
|
|
189
|
+
const record = parsed;
|
|
190
|
+
if (typeof record.sequence !== "number" || typeof record.type !== "string")
|
|
191
|
+
return null;
|
|
192
|
+
return parsed;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Stream ordered Run events as an async iterator (`GET
|
|
196
|
+
* /api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or
|
|
197
|
+
* `signal` aborts. One connection is bounded server-side (~25s or a terminal
|
|
198
|
+
* event); pass `after` to resume from the last observed sequence.
|
|
199
|
+
*/
|
|
200
|
+
export async function* streamRunEvents(transport, runId, options = {}) {
|
|
201
|
+
const body = await transport.openStream(runOperation("list_public_run_events"), {
|
|
202
|
+
pathParams: { runId },
|
|
203
|
+
...(options.after === undefined ? {} : { query: { after: options.after } }),
|
|
204
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
205
|
+
accept: "text/event-stream",
|
|
206
|
+
subject: "run-event stream",
|
|
207
|
+
});
|
|
208
|
+
for await (const frame of readSseFrames(body, options.signal)) {
|
|
209
|
+
const event = decodeRunEvent(frame.data);
|
|
210
|
+
if (event)
|
|
211
|
+
yield event;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Collect Run events until a terminal event, the deadline, or the stream ends —
|
|
216
|
+
* the bounded events-log pattern the console uses. A single pass over
|
|
217
|
+
* {@link streamRunEvents}; the deadline is enforced by aborting the stream.
|
|
218
|
+
*
|
|
219
|
+
* The deadline abort is this drain's OWN, expected termination — not a failure.
|
|
220
|
+
* Where it lands decides how {@link streamRunEvents} surfaces it: mid-read it
|
|
221
|
+
* ends the iterator cleanly, but while the underlying `fetch` is still pending —
|
|
222
|
+
* the common case for a poll whose upstream has flushed no bytes yet (no new
|
|
223
|
+
* events after the cursor, so no immediate SSE frame) — it rejects with an
|
|
224
|
+
* `aborted` {@link MaterialGraphError}. Both mean the same thing here, so the
|
|
225
|
+
* self-inflicted abort is swallowed and the collected events returned. Any other
|
|
226
|
+
* failure (a real 4xx/5xx or transport error) still propagates, keeping the
|
|
227
|
+
* fail-fast surface for genuine upstream problems.
|
|
228
|
+
*/
|
|
229
|
+
export async function drainRunEvents(transport, runId, options) {
|
|
230
|
+
const collected = [];
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timer = setTimeout(() => controller.abort(), options.deadlineMs);
|
|
233
|
+
try {
|
|
234
|
+
for await (const event of streamRunEvents(transport, runId, {
|
|
235
|
+
after: options.after,
|
|
236
|
+
signal: controller.signal,
|
|
237
|
+
})) {
|
|
238
|
+
collected.push(event);
|
|
239
|
+
if (TERMINAL_RUN_EVENT_TYPES.has(event.type))
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const causedByDeadline = controller.signal.aborted && error instanceof MaterialGraphError && error.kind === "aborted";
|
|
245
|
+
if (!causedByDeadline)
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
controller.abort();
|
|
251
|
+
}
|
|
252
|
+
return collected;
|
|
253
|
+
}
|