@opengeni/sdk 0.46.0 → 0.52.1
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 +94 -2
- package/dist/artifact-client.d.ts +20 -2
- package/dist/artifacts.d.ts +4 -0
- package/dist/artifacts.js +11 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/chunk-FHI4DFIG.js +128 -0
- package/dist/chunk-FHI4DFIG.js.map +1 -0
- package/dist/chunk-FRJFNDIR.js +218 -0
- package/dist/chunk-FRJFNDIR.js.map +1 -0
- package/dist/{chunk-QKV5OCLJ.js → chunk-ILQZR5N6.js} +1380 -595
- package/dist/chunk-ILQZR5N6.js.map +1 -0
- package/dist/chunk-MZWTWOX6.js +661 -0
- package/dist/chunk-MZWTWOX6.js.map +1 -0
- package/dist/chunk-VWE2QIVO.js +1162 -0
- package/dist/chunk-VWE2QIVO.js.map +1 -0
- package/dist/chunk-VYCTL62C.js +230 -0
- package/dist/chunk-VYCTL62C.js.map +1 -0
- package/dist/client.d.ts +176 -20
- package/dist/codex-realtime-controller.d.ts +6 -6
- package/dist/codex-realtime-lifecycle.d.ts +1 -1
- package/dist/codex-realtime-v3.d.ts +3 -3
- package/dist/codex-realtime.d.ts +1 -1
- package/dist/company-profile.d.ts +100 -0
- package/dist/core.d.ts +5 -5
- package/dist/core.js +9 -4
- package/dist/desktop.d.ts +1 -1
- package/dist/editable-artifact-resources.d.ts +96 -0
- package/dist/editable-artifacts/browser-session.d.ts +35 -0
- package/dist/editable-artifacts/controller.d.ts +60 -0
- package/dist/editable-artifacts/errors.d.ts +23 -0
- package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
- package/dist/editable-artifacts/index.d.ts +19 -0
- package/dist/editable-artifacts/pool.d.ts +15 -0
- package/dist/editable-artifacts/session.d.ts +96 -0
- package/dist/editable-artifacts/storage.d.ts +72 -0
- package/dist/editable-artifacts/types.d.ts +416 -0
- package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
- package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
- package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
- package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
- package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
- package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
- package/dist/editable-artifacts-worker.d.ts +8 -0
- package/dist/editable-artifacts-worker.js +1708 -0
- package/dist/editable-artifacts-worker.js.map +1 -0
- package/dist/editable-artifacts.d.ts +6 -0
- package/dist/editable-artifacts.js +5954 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/gateway-realtime-transport.d.ts +1 -1
- package/dist/index.d.ts +41 -35
- package/dist/index.js +104 -68
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +885 -0
- package/dist/interaction.js +55 -0
- package/dist/interaction.js.map +1 -0
- package/dist/memory-slack-client.d.ts +13 -0
- package/dist/memory-slack-delivery.d.ts +99 -0
- package/dist/memory-slack.d.ts +4 -0
- package/dist/memory-slack.js +44 -0
- package/dist/memory-slack.js.map +1 -0
- package/dist/proxy.d.ts +3 -3
- package/dist/realtime.d.ts +12 -12
- package/dist/retained-artifacts.d.ts +12 -0
- package/dist/stream.d.ts +16 -1
- package/dist/terminal.d.ts +1 -1
- package/dist/transcription.d.ts +2 -2
- package/dist/types.d.ts +1203 -23
- package/dist/workspace-control-stream.d.ts +2 -2
- package/dist/workspace-state.d.ts +1 -1
- package/package.json +29 -3
- package/src/artifact-client.ts +129 -0
- package/src/artifacts.ts +17 -0
- package/src/client.ts +1801 -206
- package/src/company-profile.ts +92 -0
- package/src/editable-artifact-resources.ts +119 -0
- package/src/editable-artifacts/browser-session.ts +176 -0
- package/src/editable-artifacts/controller.ts +2632 -0
- package/src/editable-artifacts/errors.ts +52 -0
- package/src/editable-artifacts/http-live-transport.ts +1449 -0
- package/src/editable-artifacts/index.ts +175 -0
- package/src/editable-artifacts/pool.ts +81 -0
- package/src/editable-artifacts/session.ts +778 -0
- package/src/editable-artifacts/storage.ts +1718 -0
- package/src/editable-artifacts/types.ts +499 -0
- package/src/editable-artifacts/worker/browser-client.ts +1167 -0
- package/src/editable-artifacts/worker/browser-entry.ts +57 -0
- package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
- package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
- package/src/editable-artifacts/worker/runtime.ts +1204 -0
- package/src/editable-artifacts/worker/wire-codec.ts +852 -0
- package/src/editable-artifacts-worker.ts +8 -0
- package/src/editable-artifacts.ts +6 -0
- package/src/index.ts +194 -0
- package/src/interaction.ts +1830 -0
- package/src/memory-slack-client.ts +71 -0
- package/src/memory-slack-delivery.ts +128 -0
- package/src/memory-slack.ts +19 -0
- package/src/retained-artifacts.ts +326 -0
- package/src/stream.ts +102 -4
- package/src/transcription.ts +2 -2
- package/src/types.ts +1430 -33
- package/src/workspace-control-stream.ts +15 -5
- package/dist/chunk-QKV5OCLJ.js.map +0 -1
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeSpreadsheetMetadataKernelProjection,
|
|
3
|
+
decodeSpreadsheetViewportKernelProjection,
|
|
4
|
+
EDITABLE_ARTIFACT_COMMAND_MAX_BYTES,
|
|
5
|
+
EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
|
|
6
|
+
EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES,
|
|
7
|
+
encodeSpreadsheetMetadataKernelQuery,
|
|
8
|
+
encodeSpreadsheetViewportKernelQuery,
|
|
9
|
+
spreadsheetSheetId,
|
|
10
|
+
SPREADSHEET_ARTIFACT_METADATA_MAX_SHEETS,
|
|
11
|
+
SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
|
|
12
|
+
SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS,
|
|
13
|
+
type SpreadsheetArtifactViewportQuery,
|
|
14
|
+
} from "@opengeni/contracts/editable-artifacts";
|
|
15
|
+
import {
|
|
16
|
+
decodeDocumentArtifactQueryResponse,
|
|
17
|
+
encodeDocumentArtifactQuery,
|
|
18
|
+
} from "@opengeni/contracts/document-artifact-query";
|
|
19
|
+
import {
|
|
20
|
+
decodePresentationArtifactQueryResponse,
|
|
21
|
+
encodePresentationArtifactQuery,
|
|
22
|
+
} from "@opengeni/contracts/presentation-artifact-query";
|
|
23
|
+
import type {
|
|
24
|
+
EditableDocumentProjection,
|
|
25
|
+
EditableDocumentQuery,
|
|
26
|
+
EditableArtifactModality,
|
|
27
|
+
EditableArtifactPendingTransaction,
|
|
28
|
+
EditableArtifactWorkerKernel,
|
|
29
|
+
EditableSpreadsheetMetadataQuery,
|
|
30
|
+
EditableSpreadsheetViewportQuery,
|
|
31
|
+
EditablePresentationProjection,
|
|
32
|
+
EditablePresentationQuery,
|
|
33
|
+
EditablePresentationEditorSlideProjection,
|
|
34
|
+
EditablePresentationEditorSlideQuery,
|
|
35
|
+
EditablePresentationSlideCatalogProjection,
|
|
36
|
+
EditablePresentationSlideCatalogQuery,
|
|
37
|
+
} from "../types";
|
|
38
|
+
import {
|
|
39
|
+
ArtifactWorkerRpcKind,
|
|
40
|
+
DEFAULT_ARTIFACT_WORKER_RPC_LIMITS,
|
|
41
|
+
decodeArtifactWorkerRpcMessage,
|
|
42
|
+
encodeArtifactWorkerRpcMessage,
|
|
43
|
+
ownedTransferBuffer,
|
|
44
|
+
transferListForArtifactWorkerRpcMessage,
|
|
45
|
+
type ArtifactWorkerRpcLimits,
|
|
46
|
+
type ArtifactWorkerRpcMessage,
|
|
47
|
+
} from "./rpc-protocol";
|
|
48
|
+
import {
|
|
49
|
+
decodeErrorPayload,
|
|
50
|
+
decodePendingListMetadata,
|
|
51
|
+
decodeProjectionResponse,
|
|
52
|
+
decodeStateResponse,
|
|
53
|
+
encodeAuthorPendingMetadata,
|
|
54
|
+
encodeCommittedMetadata,
|
|
55
|
+
encodeInitialize,
|
|
56
|
+
encodePendingListMetadata,
|
|
57
|
+
encodeReconcileMetadata,
|
|
58
|
+
encodeSnapshotMetadata,
|
|
59
|
+
type ArtifactWorkerInitializeInput,
|
|
60
|
+
} from "./wire-codec";
|
|
61
|
+
|
|
62
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 20_000;
|
|
63
|
+
const DEFAULT_INITIALIZATION_TIMEOUT_MS = 60_000;
|
|
64
|
+
const DEFAULT_MAX_SNAPSHOT_BYTES = EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES;
|
|
65
|
+
const DEFAULT_MAX_COMMAND_BYTES = EDITABLE_ARTIFACT_COMMAND_MAX_BYTES;
|
|
66
|
+
const DEFAULT_MAX_INTENT_BYTES = EDITABLE_ARTIFACT_INTENT_MAX_BYTES;
|
|
67
|
+
const DEFAULT_MAX_COMMITTED_TRANSACTION_BYTES = 8 * 1024 * 1024;
|
|
68
|
+
const DEFAULT_MAX_QUERY_BYTES = 256;
|
|
69
|
+
const DEFAULT_MAX_QUERY_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
70
|
+
const DEFAULT_MAX_PENDING_TRANSACTIONS = 1_024;
|
|
71
|
+
const DEFAULT_MAX_PENDING_AGGREGATE_BYTES = 64 * 1024 * 1024;
|
|
72
|
+
const MAX_CLIENT_QUEUED_REQUESTS = 64;
|
|
73
|
+
const MAX_VIEWPORT_AREA = 1_048_576;
|
|
74
|
+
const UINT32_MAX = 0xffff_ffff;
|
|
75
|
+
|
|
76
|
+
export type ArtifactWorkerClientMessageEvent = { data: unknown };
|
|
77
|
+
export type ArtifactWorkerClientErrorEvent = { error?: unknown; message?: string };
|
|
78
|
+
|
|
79
|
+
export type ArtifactWorkerClientEndpoint = {
|
|
80
|
+
postMessage: (message: ArtifactWorkerRpcMessage, transfer: Transferable[]) => void;
|
|
81
|
+
addEventListener: {
|
|
82
|
+
(type: "message", listener: (event: ArtifactWorkerClientMessageEvent) => void): void;
|
|
83
|
+
(
|
|
84
|
+
type: "error" | "messageerror",
|
|
85
|
+
listener: (event: ArtifactWorkerClientErrorEvent) => void,
|
|
86
|
+
): void;
|
|
87
|
+
};
|
|
88
|
+
removeEventListener: {
|
|
89
|
+
(type: "message", listener: (event: ArtifactWorkerClientMessageEvent) => void): void;
|
|
90
|
+
(
|
|
91
|
+
type: "error" | "messageerror",
|
|
92
|
+
listener: (event: ArtifactWorkerClientErrorEvent) => void,
|
|
93
|
+
): void;
|
|
94
|
+
};
|
|
95
|
+
terminate: () => void;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type CreateBrowserEditableArtifactWorkerKernelOptions = {
|
|
99
|
+
modality: EditableArtifactModality;
|
|
100
|
+
/** Exact package/runtime identity; checked before the Worker accepts state. */
|
|
101
|
+
kernelVersion: string;
|
|
102
|
+
protocolVersion: number;
|
|
103
|
+
modelSchemaVersion: number;
|
|
104
|
+
commandVersion: number;
|
|
105
|
+
/**
|
|
106
|
+
* Prebuilt module-Worker entry. No blob/eval worker is ever synthesized.
|
|
107
|
+
* Bundler example: import `workerUrl` from
|
|
108
|
+
* `@opengeni/sdk/editable-artifacts/worker?worker&url`, then pass it here.
|
|
109
|
+
*/
|
|
110
|
+
workerUrl: string | URL;
|
|
111
|
+
/** wasm-bindgen ESM glue built from the pinned artifact kernel. */
|
|
112
|
+
wasmGlueUrl: string | URL;
|
|
113
|
+
/** Exact `.wasm` asset paired with the glue. */
|
|
114
|
+
wasmBinaryUrl: string | URL;
|
|
115
|
+
/**
|
|
116
|
+
* Origin hosting the embedding application. Browser runtimes default to
|
|
117
|
+
* `location.origin`; non-browser/custom Worker hosts must supply it.
|
|
118
|
+
*/
|
|
119
|
+
applicationOrigin?: string | URL;
|
|
120
|
+
/** Additional explicitly trusted asset origins; same-origin is the default. */
|
|
121
|
+
allowedAssetOrigins?: readonly string[];
|
|
122
|
+
/** Only for local development. Production assets must use HTTPS. */
|
|
123
|
+
allowInsecureDevelopmentAssets?: boolean;
|
|
124
|
+
requestTimeoutMs?: number;
|
|
125
|
+
initializationTimeoutMs?: number;
|
|
126
|
+
maximumSnapshotBytes?: number;
|
|
127
|
+
maximumCommandBytes?: number;
|
|
128
|
+
maximumIntentBytes?: number;
|
|
129
|
+
maximumCommittedTransactionBytes?: number;
|
|
130
|
+
maximumQueryBytes?: number;
|
|
131
|
+
maximumQueryResponseBytes?: number;
|
|
132
|
+
maximumPendingTransactions?: number;
|
|
133
|
+
maximumPendingAggregateBytes?: number;
|
|
134
|
+
rpcLimits?: Readonly<ArtifactWorkerRpcLimits>;
|
|
135
|
+
workerFactory?: (
|
|
136
|
+
url: URL,
|
|
137
|
+
options: { type: "module"; name: string },
|
|
138
|
+
) => ArtifactWorkerClientEndpoint;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export type BrowserEditableArtifactWorkerKernel = EditableArtifactWorkerKernel & {
|
|
142
|
+
queryPresentationSlideCatalog: (
|
|
143
|
+
query: EditablePresentationSlideCatalogQuery,
|
|
144
|
+
) => Promise<EditablePresentationSlideCatalogProjection>;
|
|
145
|
+
queryPresentationEditorSlide: (
|
|
146
|
+
query: EditablePresentationEditorSlideQuery,
|
|
147
|
+
) => Promise<EditablePresentationEditorSlideProjection>;
|
|
148
|
+
/** Requests bounded graceful cleanup, then terminates the Worker. Idempotent. */
|
|
149
|
+
dispose: () => Promise<void>;
|
|
150
|
+
/** Retires a failed generation; the next method call lazily starts a fresh Worker. */
|
|
151
|
+
restart: () => void;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export class ArtifactWorkerClientError extends Error {
|
|
155
|
+
readonly code: string;
|
|
156
|
+
readonly retryable: boolean;
|
|
157
|
+
|
|
158
|
+
constructor(
|
|
159
|
+
code: string,
|
|
160
|
+
message: string,
|
|
161
|
+
options: { retryable?: boolean; cause?: unknown } = {},
|
|
162
|
+
) {
|
|
163
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
164
|
+
this.name = "ArtifactWorkerClientError";
|
|
165
|
+
this.code = code;
|
|
166
|
+
this.retryable = options.retryable ?? false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type RpcResult = {
|
|
171
|
+
metadata: Uint8Array;
|
|
172
|
+
segments: Uint8Array[];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
type PendingRequest = {
|
|
176
|
+
resolve: (result: RpcResult) => void;
|
|
177
|
+
reject: (error: Error) => void;
|
|
178
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
type WorkerGeneration = {
|
|
182
|
+
endpoint: ArtifactWorkerClientEndpoint;
|
|
183
|
+
generation: number;
|
|
184
|
+
nextRequestId: number;
|
|
185
|
+
pending: Map<number, PendingRequest>;
|
|
186
|
+
ready: Promise<void>;
|
|
187
|
+
initialized: boolean;
|
|
188
|
+
onMessage: (event: ArtifactWorkerClientMessageEvent) => void;
|
|
189
|
+
onError: (event: ArtifactWorkerClientErrorEvent) => void;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export function createBrowserEditableArtifactWorkerKernel(
|
|
193
|
+
options: CreateBrowserEditableArtifactWorkerKernelOptions,
|
|
194
|
+
): BrowserEditableArtifactWorkerKernel {
|
|
195
|
+
return new BrowserArtifactWorkerKernelClient(options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
class BrowserArtifactWorkerKernelClient implements BrowserEditableArtifactWorkerKernel {
|
|
199
|
+
private readonly workerUrl: URL;
|
|
200
|
+
private readonly initialization: ArtifactWorkerInitializeInput;
|
|
201
|
+
private readonly requestTimeoutMs: number;
|
|
202
|
+
private readonly initializationTimeoutMs: number;
|
|
203
|
+
private readonly maximumPendingAggregateBytes: number;
|
|
204
|
+
private readonly maximumQueuedTransferBytes: number;
|
|
205
|
+
private readonly rpcLimits: Readonly<ArtifactWorkerRpcLimits>;
|
|
206
|
+
private readonly workerFactory: NonNullable<
|
|
207
|
+
CreateBrowserEditableArtifactWorkerKernelOptions["workerFactory"]
|
|
208
|
+
>;
|
|
209
|
+
private generationCounter = 0;
|
|
210
|
+
private admittedRequests = 0;
|
|
211
|
+
private admittedTransferBytes = 0;
|
|
212
|
+
private active: WorkerGeneration | null = null;
|
|
213
|
+
private disposed = false;
|
|
214
|
+
|
|
215
|
+
constructor(options: CreateBrowserEditableArtifactWorkerKernelOptions) {
|
|
216
|
+
this.workerUrl = canonicalUrl(options.workerUrl, "workerUrl");
|
|
217
|
+
const wasmGlueUrl = canonicalUrl(options.wasmGlueUrl, "wasmGlueUrl");
|
|
218
|
+
const wasmBinaryUrl = canonicalUrl(options.wasmBinaryUrl, "wasmBinaryUrl");
|
|
219
|
+
validateAssetPolicy(
|
|
220
|
+
[this.workerUrl, wasmGlueUrl, wasmBinaryUrl],
|
|
221
|
+
resolveApplicationOrigin(options.applicationOrigin),
|
|
222
|
+
options.allowedAssetOrigins ?? [],
|
|
223
|
+
options.allowInsecureDevelopmentAssets ?? false,
|
|
224
|
+
);
|
|
225
|
+
const modality = requireModality(options.modality);
|
|
226
|
+
const modalityQueryBytes =
|
|
227
|
+
modality === "document" ? 256 : modality === "presentation" ? 96 : 68;
|
|
228
|
+
this.initialization = {
|
|
229
|
+
modality,
|
|
230
|
+
kernelVersion: boundedVersion(options.kernelVersion, "kernelVersion"),
|
|
231
|
+
protocolVersion: positiveVersion(options.protocolVersion, "protocolVersion"),
|
|
232
|
+
modelSchemaVersion: positiveVersion(options.modelSchemaVersion, "modelSchemaVersion"),
|
|
233
|
+
commandVersion: positiveVersion(options.commandVersion, "commandVersion"),
|
|
234
|
+
wasmGlueUrl: wasmGlueUrl.href,
|
|
235
|
+
wasmBinaryUrl: wasmBinaryUrl.href,
|
|
236
|
+
maximumSnapshotBytes: boundedMaximum(
|
|
237
|
+
options.maximumSnapshotBytes ?? DEFAULT_MAX_SNAPSHOT_BYTES,
|
|
238
|
+
DEFAULT_MAX_SNAPSHOT_BYTES,
|
|
239
|
+
"maximumSnapshotBytes",
|
|
240
|
+
),
|
|
241
|
+
maximumCommandBytes: boundedMaximum(
|
|
242
|
+
options.maximumCommandBytes ?? DEFAULT_MAX_COMMAND_BYTES,
|
|
243
|
+
DEFAULT_MAX_COMMAND_BYTES,
|
|
244
|
+
"maximumCommandBytes",
|
|
245
|
+
),
|
|
246
|
+
maximumIntentBytes: boundedMaximum(
|
|
247
|
+
options.maximumIntentBytes ?? DEFAULT_MAX_INTENT_BYTES,
|
|
248
|
+
DEFAULT_MAX_INTENT_BYTES,
|
|
249
|
+
"maximumIntentBytes",
|
|
250
|
+
),
|
|
251
|
+
maximumCommittedTransactionBytes: boundedMaximum(
|
|
252
|
+
options.maximumCommittedTransactionBytes ?? DEFAULT_MAX_COMMITTED_TRANSACTION_BYTES,
|
|
253
|
+
DEFAULT_MAX_COMMITTED_TRANSACTION_BYTES,
|
|
254
|
+
"maximumCommittedTransactionBytes",
|
|
255
|
+
),
|
|
256
|
+
maximumQueryBytes: boundedMaximum(
|
|
257
|
+
options.maximumQueryBytes ?? modalityQueryBytes,
|
|
258
|
+
DEFAULT_MAX_QUERY_BYTES,
|
|
259
|
+
"maximumQueryBytes",
|
|
260
|
+
),
|
|
261
|
+
maximumQueryResponseBytes: boundedMaximum(
|
|
262
|
+
options.maximumQueryResponseBytes ?? DEFAULT_MAX_QUERY_RESPONSE_BYTES,
|
|
263
|
+
DEFAULT_MAX_QUERY_RESPONSE_BYTES,
|
|
264
|
+
"maximumQueryResponseBytes",
|
|
265
|
+
),
|
|
266
|
+
maximumPendingTransactions: boundedMaximum(
|
|
267
|
+
options.maximumPendingTransactions ?? DEFAULT_MAX_PENDING_TRANSACTIONS,
|
|
268
|
+
DEFAULT_MAX_PENDING_TRANSACTIONS,
|
|
269
|
+
"maximumPendingTransactions",
|
|
270
|
+
),
|
|
271
|
+
};
|
|
272
|
+
this.maximumPendingAggregateBytes = boundedMaximum(
|
|
273
|
+
options.maximumPendingAggregateBytes ?? DEFAULT_MAX_PENDING_AGGREGATE_BYTES,
|
|
274
|
+
DEFAULT_MAX_PENDING_AGGREGATE_BYTES,
|
|
275
|
+
"maximumPendingAggregateBytes",
|
|
276
|
+
);
|
|
277
|
+
this.maximumQueuedTransferBytes = Math.max(
|
|
278
|
+
this.initialization.maximumSnapshotBytes,
|
|
279
|
+
this.initialization.maximumCommittedTransactionBytes + this.maximumPendingAggregateBytes,
|
|
280
|
+
this.initialization.maximumCommandBytes,
|
|
281
|
+
);
|
|
282
|
+
this.requestTimeoutMs = positiveSafeInteger(
|
|
283
|
+
options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
284
|
+
"requestTimeoutMs",
|
|
285
|
+
);
|
|
286
|
+
this.initializationTimeoutMs = positiveSafeInteger(
|
|
287
|
+
options.initializationTimeoutMs ?? DEFAULT_INITIALIZATION_TIMEOUT_MS,
|
|
288
|
+
"initializationTimeoutMs",
|
|
289
|
+
);
|
|
290
|
+
this.rpcLimits = normalizeClientRpcLimits(
|
|
291
|
+
options.rpcLimits ?? DEFAULT_ARTIFACT_WORKER_RPC_LIMITS,
|
|
292
|
+
);
|
|
293
|
+
this.workerFactory = options.workerFactory ?? defaultWorkerFactory;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async reset(): Promise<void> {
|
|
297
|
+
return await this.withAdmission(0, async () => {
|
|
298
|
+
const result = await this.request(ArtifactWorkerRpcKind.Reset);
|
|
299
|
+
requireEmptyResult(result, "reset");
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async loadSnapshot(
|
|
304
|
+
snapshot: Parameters<EditableArtifactWorkerKernel["loadSnapshot"]>[0],
|
|
305
|
+
): ReturnType<EditableArtifactWorkerKernel["loadSnapshot"]> {
|
|
306
|
+
const transferBytes = this.segmentByteLength(
|
|
307
|
+
snapshot.bytes,
|
|
308
|
+
this.initialization.maximumSnapshotBytes,
|
|
309
|
+
"snapshot",
|
|
310
|
+
);
|
|
311
|
+
return await this.withAdmission(transferBytes, async () => {
|
|
312
|
+
const segment = this.ownedSegment(
|
|
313
|
+
snapshot.bytes,
|
|
314
|
+
this.initialization.maximumSnapshotBytes,
|
|
315
|
+
"snapshot",
|
|
316
|
+
);
|
|
317
|
+
const result = await this.request(
|
|
318
|
+
ArtifactWorkerRpcKind.LoadSnapshot,
|
|
319
|
+
encodeSnapshotMetadata(snapshot),
|
|
320
|
+
[segment],
|
|
321
|
+
);
|
|
322
|
+
if (result.segments.length !== 0)
|
|
323
|
+
throw clientError("invalid_response", "snapshot response has segments");
|
|
324
|
+
const decoded = decodeStateResponse(result.metadata);
|
|
325
|
+
if (!decoded.digest)
|
|
326
|
+
throw clientError("invalid_response", "snapshot response omitted its digest");
|
|
327
|
+
return { stateHash: decoded.stateHash, digest: decoded.digest };
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async applyRecovered(
|
|
332
|
+
transaction: Parameters<EditableArtifactWorkerKernel["applyRecovered"]>[0],
|
|
333
|
+
): ReturnType<EditableArtifactWorkerKernel["applyRecovered"]> {
|
|
334
|
+
const transferBytes = this.segmentByteLength(
|
|
335
|
+
transaction.committedTransactionBytes,
|
|
336
|
+
this.initialization.maximumCommittedTransactionBytes,
|
|
337
|
+
"committed operation",
|
|
338
|
+
);
|
|
339
|
+
return await this.withAdmission(transferBytes, async () => {
|
|
340
|
+
const segment = this.ownedSegment(
|
|
341
|
+
transaction.committedTransactionBytes,
|
|
342
|
+
this.initialization.maximumCommittedTransactionBytes,
|
|
343
|
+
"committed operation",
|
|
344
|
+
);
|
|
345
|
+
const result = await this.request(
|
|
346
|
+
ArtifactWorkerRpcKind.ApplyRecovered,
|
|
347
|
+
encodeCommittedMetadata(transaction),
|
|
348
|
+
[segment],
|
|
349
|
+
);
|
|
350
|
+
if (result.segments.length !== 0)
|
|
351
|
+
throw clientError("invalid_response", "apply response has segments");
|
|
352
|
+
const decoded = decodeStateResponse(result.metadata);
|
|
353
|
+
if (decoded.digest)
|
|
354
|
+
throw clientError("invalid_response", "apply response unexpectedly contains a digest");
|
|
355
|
+
return { stateHash: decoded.stateHash };
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async reconcileCommitted(
|
|
360
|
+
transaction: Parameters<EditableArtifactWorkerKernel["reconcileCommitted"]>[0],
|
|
361
|
+
remainingPending: Parameters<EditableArtifactWorkerKernel["reconcileCommitted"]>[1],
|
|
362
|
+
): ReturnType<EditableArtifactWorkerKernel["reconcileCommitted"]> {
|
|
363
|
+
const transferBytes = this.measurePendingTransfer(
|
|
364
|
+
remainingPending,
|
|
365
|
+
transaction.committedTransactionBytes,
|
|
366
|
+
);
|
|
367
|
+
return await this.withAdmission(transferBytes, async () => {
|
|
368
|
+
const segments = this.pendingSegments(
|
|
369
|
+
remainingPending,
|
|
370
|
+
transaction.committedTransactionBytes,
|
|
371
|
+
);
|
|
372
|
+
const result = await this.request(
|
|
373
|
+
ArtifactWorkerRpcKind.ReconcileCommitted,
|
|
374
|
+
encodeReconcileMetadata(transaction, remainingPending),
|
|
375
|
+
segments,
|
|
376
|
+
);
|
|
377
|
+
if (result.segments.length !== 0) {
|
|
378
|
+
throw clientError("invalid_response", "reconcile response has segments");
|
|
379
|
+
}
|
|
380
|
+
const decoded = decodeProjectionResponse(result.metadata);
|
|
381
|
+
if (!decoded.stateHash)
|
|
382
|
+
throw clientError("invalid_response", "reconcile response omitted stateHash");
|
|
383
|
+
return { stateHash: decoded.stateHash, blockedPending: decoded.blockedPending };
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async replacePending(
|
|
388
|
+
transactions: Parameters<EditableArtifactWorkerKernel["replacePending"]>[0],
|
|
389
|
+
): ReturnType<EditableArtifactWorkerKernel["replacePending"]> {
|
|
390
|
+
const transferBytes = this.measurePendingTransfer(transactions);
|
|
391
|
+
return await this.withAdmission(transferBytes, async () => {
|
|
392
|
+
const segments = this.pendingSegments(transactions);
|
|
393
|
+
const result = await this.request(
|
|
394
|
+
ArtifactWorkerRpcKind.ReplacePending,
|
|
395
|
+
encodePendingListMetadata(transactions),
|
|
396
|
+
segments,
|
|
397
|
+
);
|
|
398
|
+
if (result.segments.length !== 0) {
|
|
399
|
+
throw clientError("invalid_response", "pending response has segments");
|
|
400
|
+
}
|
|
401
|
+
const decoded = decodeProjectionResponse(result.metadata);
|
|
402
|
+
if (decoded.stateHash) {
|
|
403
|
+
throw clientError("invalid_response", "pending response unexpectedly contains stateHash");
|
|
404
|
+
}
|
|
405
|
+
return { blockedPending: decoded.blockedPending };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async authorPending(
|
|
410
|
+
input: Parameters<EditableArtifactWorkerKernel["authorPending"]>[0],
|
|
411
|
+
): ReturnType<EditableArtifactWorkerKernel["authorPending"]> {
|
|
412
|
+
if (input.modality !== this.initialization.modality) {
|
|
413
|
+
throw clientError("modality_mismatch", "pending modality does not match Worker", false);
|
|
414
|
+
}
|
|
415
|
+
const transferBytes = this.segmentByteLength(
|
|
416
|
+
input.commandBytes,
|
|
417
|
+
this.initialization.maximumCommandBytes,
|
|
418
|
+
"pending command",
|
|
419
|
+
);
|
|
420
|
+
return await this.withAdmission(transferBytes, async () => {
|
|
421
|
+
const segment = this.ownedSegment(
|
|
422
|
+
input.commandBytes,
|
|
423
|
+
this.initialization.maximumCommandBytes,
|
|
424
|
+
"pending command",
|
|
425
|
+
);
|
|
426
|
+
const result = await this.request(
|
|
427
|
+
ArtifactWorkerRpcKind.AuthorPending,
|
|
428
|
+
encodeAuthorPendingMetadata(input),
|
|
429
|
+
[segment],
|
|
430
|
+
);
|
|
431
|
+
const pending = decodePendingListMetadata(
|
|
432
|
+
result.metadata,
|
|
433
|
+
result.segments,
|
|
434
|
+
this.initialization.maximumPendingTransactions,
|
|
435
|
+
);
|
|
436
|
+
if (pending.length !== 1)
|
|
437
|
+
throw clientError("invalid_response", "author response count is invalid");
|
|
438
|
+
return pending[0]!;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async querySpreadsheetViewport(
|
|
443
|
+
query: EditableSpreadsheetViewportQuery,
|
|
444
|
+
): ReturnType<EditableArtifactWorkerKernel["querySpreadsheetViewport"]> {
|
|
445
|
+
if (this.initialization.modality !== "spreadsheet") {
|
|
446
|
+
throw clientError(
|
|
447
|
+
"modality_mismatch",
|
|
448
|
+
"spreadsheet query requires a spreadsheet Worker",
|
|
449
|
+
false,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const normalized = normalizeViewportQuery(query);
|
|
453
|
+
const queryBytes = encodeSpreadsheetViewportKernelQuery(normalized);
|
|
454
|
+
return await this.withAdmission(queryBytes.byteLength, async () => {
|
|
455
|
+
const result = await this.request(ArtifactWorkerRpcKind.QuerySpreadsheet, undefined, [
|
|
456
|
+
ownedTransferBuffer(queryBytes),
|
|
457
|
+
]);
|
|
458
|
+
if (result.metadata.byteLength !== 0 || result.segments.length !== 1) {
|
|
459
|
+
throw clientError("invalid_response", "spreadsheet viewport response is malformed");
|
|
460
|
+
}
|
|
461
|
+
return decodeSpreadsheetViewportKernelProjection(result.segments[0]!, normalized);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async querySpreadsheetMetadata(
|
|
466
|
+
query: EditableSpreadsheetMetadataQuery = {},
|
|
467
|
+
): ReturnType<EditableArtifactWorkerKernel["querySpreadsheetMetadata"]> {
|
|
468
|
+
if (this.initialization.modality !== "spreadsheet") {
|
|
469
|
+
throw clientError(
|
|
470
|
+
"modality_mismatch",
|
|
471
|
+
"spreadsheet query requires a spreadsheet Worker",
|
|
472
|
+
false,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
const normalized = normalizeMetadataQuery(query);
|
|
476
|
+
const queryBytes = encodeSpreadsheetMetadataKernelQuery(normalized);
|
|
477
|
+
return await this.withAdmission(queryBytes.byteLength, async () => {
|
|
478
|
+
const result = await this.request(ArtifactWorkerRpcKind.QuerySpreadsheet, undefined, [
|
|
479
|
+
ownedTransferBuffer(queryBytes),
|
|
480
|
+
]);
|
|
481
|
+
if (result.metadata.byteLength !== 0 || result.segments.length !== 1) {
|
|
482
|
+
throw clientError("invalid_response", "spreadsheet metadata response is malformed");
|
|
483
|
+
}
|
|
484
|
+
return decodeSpreadsheetMetadataKernelProjection(result.segments[0]!, normalized);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async queryDocument(query: EditableDocumentQuery): Promise<EditableDocumentProjection> {
|
|
489
|
+
if (this.initialization.modality !== "document") {
|
|
490
|
+
throw clientError("modality_mismatch", "document query requires a document Worker", false);
|
|
491
|
+
}
|
|
492
|
+
const queryBytes = encodeDocumentArtifactQuery(query);
|
|
493
|
+
return await this.withAdmission(queryBytes.byteLength, async () => {
|
|
494
|
+
const result = await this.request(ArtifactWorkerRpcKind.QuerySpreadsheet, undefined, [
|
|
495
|
+
ownedTransferBuffer(queryBytes),
|
|
496
|
+
]);
|
|
497
|
+
if (result.metadata.byteLength !== 0 || result.segments.length !== 1) {
|
|
498
|
+
throw clientError("invalid_response", "document projection response is malformed");
|
|
499
|
+
}
|
|
500
|
+
return decodeDocumentArtifactQueryResponse(result.segments[0]!);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async queryPresentation(
|
|
505
|
+
query: EditablePresentationQuery,
|
|
506
|
+
): Promise<EditablePresentationProjection> {
|
|
507
|
+
if (this.initialization.modality !== "presentation") {
|
|
508
|
+
throw clientError(
|
|
509
|
+
"modality_mismatch",
|
|
510
|
+
"presentation query requires a presentation Worker",
|
|
511
|
+
false,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const queryBytes = encodePresentationArtifactQuery(query);
|
|
515
|
+
return await this.withAdmission(queryBytes.byteLength, async () => {
|
|
516
|
+
const result = await this.request(ArtifactWorkerRpcKind.QuerySpreadsheet, undefined, [
|
|
517
|
+
ownedTransferBuffer(queryBytes),
|
|
518
|
+
]);
|
|
519
|
+
if (result.metadata.byteLength !== 0 || result.segments.length !== 1) {
|
|
520
|
+
throw clientError("invalid_response", "presentation projection response is malformed");
|
|
521
|
+
}
|
|
522
|
+
if (result.segments[0]!.byteLength > query.maxBytes) {
|
|
523
|
+
throw clientError(
|
|
524
|
+
"invalid_response",
|
|
525
|
+
"presentation projection exceeds its requested bound",
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const response = decodePresentationArtifactQueryResponse(result.segments[0]!);
|
|
529
|
+
assertPresentationProjectionMatchesQuery(query, response);
|
|
530
|
+
return response;
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async queryPresentationSlideCatalog(
|
|
535
|
+
query: EditablePresentationSlideCatalogQuery,
|
|
536
|
+
): Promise<EditablePresentationSlideCatalogProjection> {
|
|
537
|
+
const response = await this.queryPresentation(query);
|
|
538
|
+
if (response.kind !== "slide-catalog") {
|
|
539
|
+
throw clientError("invalid_response", "presentation slide catalog response is malformed");
|
|
540
|
+
}
|
|
541
|
+
return response;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async queryPresentationEditorSlide(
|
|
545
|
+
query: EditablePresentationEditorSlideQuery,
|
|
546
|
+
): Promise<EditablePresentationEditorSlideProjection> {
|
|
547
|
+
const response = await this.queryPresentation(query);
|
|
548
|
+
if (response.kind !== "editor-slide") {
|
|
549
|
+
throw clientError("invalid_response", "presentation editor slide response is malformed");
|
|
550
|
+
}
|
|
551
|
+
return response;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
restart(): void {
|
|
555
|
+
if (this.disposed) throw clientError("disposed", "artifact Worker client is disposed");
|
|
556
|
+
this.retire(
|
|
557
|
+
this.active,
|
|
558
|
+
clientError("worker_restarted", "artifact Worker generation was retired", true),
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async dispose(): Promise<void> {
|
|
563
|
+
if (this.disposed) return;
|
|
564
|
+
this.disposed = true;
|
|
565
|
+
const active = this.active;
|
|
566
|
+
if (active?.initialized && this.active === active) {
|
|
567
|
+
await this.requestOn(
|
|
568
|
+
active,
|
|
569
|
+
ArtifactWorkerRpcKind.Dispose,
|
|
570
|
+
undefined,
|
|
571
|
+
undefined,
|
|
572
|
+
1_000,
|
|
573
|
+
).catch(() => {});
|
|
574
|
+
}
|
|
575
|
+
this.retire(active, clientError("disposed", "artifact Worker client is disposed"));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private async request(
|
|
579
|
+
kind: ArtifactWorkerRpcKind,
|
|
580
|
+
metadata?: Uint8Array,
|
|
581
|
+
segments?: ArrayBuffer[],
|
|
582
|
+
): Promise<RpcResult> {
|
|
583
|
+
const active = await this.ensureActive();
|
|
584
|
+
return await this.requestOn(active, kind, metadata, segments, this.requestTimeoutMs);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private async ensureActive(): Promise<WorkerGeneration> {
|
|
588
|
+
if (this.disposed) throw clientError("disposed", "artifact Worker client is disposed");
|
|
589
|
+
if (!this.active) this.spawn();
|
|
590
|
+
const active = this.active!;
|
|
591
|
+
await active.ready;
|
|
592
|
+
if (this.active !== active) return await this.ensureActive();
|
|
593
|
+
return active;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private spawn(): void {
|
|
597
|
+
this.generationCounter += 1;
|
|
598
|
+
if (this.generationCounter > 0xffffffff) {
|
|
599
|
+
throw clientError("generation_exhausted", "artifact Worker generation space is exhausted");
|
|
600
|
+
}
|
|
601
|
+
const endpoint = this.workerFactory(this.workerUrl, {
|
|
602
|
+
type: "module",
|
|
603
|
+
name: `opengeni-artifact-${this.generationCounter}`,
|
|
604
|
+
});
|
|
605
|
+
const active: WorkerGeneration = {
|
|
606
|
+
endpoint,
|
|
607
|
+
generation: this.generationCounter,
|
|
608
|
+
nextRequestId: 1,
|
|
609
|
+
pending: new Map(),
|
|
610
|
+
ready: Promise.resolve(),
|
|
611
|
+
initialized: false,
|
|
612
|
+
onMessage: () => {},
|
|
613
|
+
onError: () => {},
|
|
614
|
+
};
|
|
615
|
+
active.onMessage = (event) => this.receive(active, event);
|
|
616
|
+
active.onError = (event) => {
|
|
617
|
+
this.retire(
|
|
618
|
+
active,
|
|
619
|
+
clientError(
|
|
620
|
+
"worker_crashed",
|
|
621
|
+
event.message || "artifact Worker crashed",
|
|
622
|
+
true,
|
|
623
|
+
event.error,
|
|
624
|
+
),
|
|
625
|
+
);
|
|
626
|
+
};
|
|
627
|
+
endpoint.addEventListener("message", active.onMessage);
|
|
628
|
+
endpoint.addEventListener("error", active.onError);
|
|
629
|
+
endpoint.addEventListener("messageerror", active.onError);
|
|
630
|
+
this.active = active;
|
|
631
|
+
active.ready = this.requestOn(
|
|
632
|
+
active,
|
|
633
|
+
ArtifactWorkerRpcKind.Initialize,
|
|
634
|
+
encodeInitialize(this.initialization),
|
|
635
|
+
undefined,
|
|
636
|
+
this.initializationTimeoutMs,
|
|
637
|
+
)
|
|
638
|
+
.then((result) => {
|
|
639
|
+
requireEmptyResult(result, "initialize");
|
|
640
|
+
active.initialized = true;
|
|
641
|
+
})
|
|
642
|
+
.catch((error: unknown) => {
|
|
643
|
+
const failure =
|
|
644
|
+
error instanceof Error
|
|
645
|
+
? error
|
|
646
|
+
: clientError("initialization_failed", "artifact Worker initialization failed", true);
|
|
647
|
+
this.retire(active, failure);
|
|
648
|
+
throw failure;
|
|
649
|
+
});
|
|
650
|
+
active.ready.catch(() => {});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private requestOn(
|
|
654
|
+
active: WorkerGeneration,
|
|
655
|
+
kind: ArtifactWorkerRpcKind,
|
|
656
|
+
metadata: Uint8Array | undefined,
|
|
657
|
+
segments: ArrayBuffer[] | undefined,
|
|
658
|
+
timeoutMs: number,
|
|
659
|
+
): Promise<RpcResult> {
|
|
660
|
+
if (this.active !== active) {
|
|
661
|
+
return Promise.reject(
|
|
662
|
+
clientError("stale_generation", "artifact Worker generation is retired", true),
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
const requestId = nextRequestId(active);
|
|
666
|
+
const message = encodeArtifactWorkerRpcMessage({
|
|
667
|
+
kind,
|
|
668
|
+
generation: active.generation,
|
|
669
|
+
requestId,
|
|
670
|
+
...(metadata ? { metadata } : {}),
|
|
671
|
+
...(segments ? { segments } : {}),
|
|
672
|
+
limits: this.rpcLimits,
|
|
673
|
+
});
|
|
674
|
+
return new Promise<RpcResult>((resolve, reject) => {
|
|
675
|
+
const timeout = setTimeout(() => {
|
|
676
|
+
if (!active.pending.has(requestId)) return;
|
|
677
|
+
this.sendCancel(active, requestId);
|
|
678
|
+
this.retire(
|
|
679
|
+
active,
|
|
680
|
+
clientError("worker_timeout", `artifact Worker request exceeded ${timeoutMs}ms`, true),
|
|
681
|
+
);
|
|
682
|
+
}, timeoutMs);
|
|
683
|
+
active.pending.set(requestId, { resolve, reject, timeout });
|
|
684
|
+
try {
|
|
685
|
+
active.endpoint.postMessage(message, transferListForArtifactWorkerRpcMessage(message));
|
|
686
|
+
} catch (error) {
|
|
687
|
+
clearTimeout(timeout);
|
|
688
|
+
active.pending.delete(requestId);
|
|
689
|
+
const failure = clientError(
|
|
690
|
+
"post_failed",
|
|
691
|
+
"failed to post artifact Worker request",
|
|
692
|
+
true,
|
|
693
|
+
error,
|
|
694
|
+
);
|
|
695
|
+
reject(failure);
|
|
696
|
+
this.retire(active, failure);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private receive(active: WorkerGeneration, event: ArtifactWorkerClientMessageEvent): void {
|
|
702
|
+
if (this.active !== active) return;
|
|
703
|
+
let frame;
|
|
704
|
+
try {
|
|
705
|
+
frame = decodeArtifactWorkerRpcMessage(event.data, this.rpcLimits);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
this.retire(
|
|
708
|
+
active,
|
|
709
|
+
clientError("invalid_response", "artifact Worker sent an invalid frame", true, error),
|
|
710
|
+
);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (frame.generation !== active.generation) {
|
|
714
|
+
this.retire(
|
|
715
|
+
active,
|
|
716
|
+
clientError("invalid_response", "artifact Worker response generation is invalid", true),
|
|
717
|
+
);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (
|
|
721
|
+
frame.flags !== 0 ||
|
|
722
|
+
(frame.kind !== ArtifactWorkerRpcKind.Response && frame.kind !== ArtifactWorkerRpcKind.Error)
|
|
723
|
+
) {
|
|
724
|
+
this.retire(
|
|
725
|
+
active,
|
|
726
|
+
clientError("invalid_response", "artifact Worker sent an unexpected frame", true),
|
|
727
|
+
);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const pending = active.pending.get(frame.requestId);
|
|
731
|
+
if (!pending) return;
|
|
732
|
+
active.pending.delete(frame.requestId);
|
|
733
|
+
clearTimeout(pending.timeout);
|
|
734
|
+
if (frame.kind === ArtifactWorkerRpcKind.Error) {
|
|
735
|
+
if (frame.segments.length !== 0) {
|
|
736
|
+
pending.reject(clientError("invalid_response", "artifact Worker error has segments", true));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const error = decodeErrorPayload(frame.metadata);
|
|
741
|
+
pending.reject(
|
|
742
|
+
new ArtifactWorkerClientError(error.code, error.message, { retryable: error.retryable }),
|
|
743
|
+
);
|
|
744
|
+
} catch (decodeError) {
|
|
745
|
+
pending.reject(
|
|
746
|
+
clientError("invalid_response", "artifact Worker error is malformed", true, decodeError),
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
pending.resolve({ metadata: frame.metadata, segments: frame.segments });
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private sendCancel(active: WorkerGeneration, requestId: number): void {
|
|
755
|
+
if (this.active !== active) return;
|
|
756
|
+
try {
|
|
757
|
+
const message = encodeArtifactWorkerRpcMessage({
|
|
758
|
+
kind: ArtifactWorkerRpcKind.Cancel,
|
|
759
|
+
generation: active.generation,
|
|
760
|
+
requestId,
|
|
761
|
+
limits: this.rpcLimits,
|
|
762
|
+
});
|
|
763
|
+
active.endpoint.postMessage(message, transferListForArtifactWorkerRpcMessage(message));
|
|
764
|
+
} catch {
|
|
765
|
+
// The watchdog immediately terminates the generation; cancel is best-effort.
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
private retire(active: WorkerGeneration | null, error: Error): void {
|
|
770
|
+
if (!active || this.active !== active) return;
|
|
771
|
+
this.active = null;
|
|
772
|
+
active.endpoint.removeEventListener("message", active.onMessage);
|
|
773
|
+
active.endpoint.removeEventListener("error", active.onError);
|
|
774
|
+
active.endpoint.removeEventListener("messageerror", active.onError);
|
|
775
|
+
active.endpoint.terminate();
|
|
776
|
+
for (const request of active.pending.values()) {
|
|
777
|
+
clearTimeout(request.timeout);
|
|
778
|
+
request.reject(error);
|
|
779
|
+
}
|
|
780
|
+
active.pending.clear();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private pendingSegments(
|
|
784
|
+
transactions: readonly EditableArtifactPendingTransaction[],
|
|
785
|
+
committedTransaction?: Uint8Array,
|
|
786
|
+
): ArrayBuffer[] {
|
|
787
|
+
const segments: ArrayBuffer[] = [];
|
|
788
|
+
if (committedTransaction) {
|
|
789
|
+
const segment = this.ownedSegment(
|
|
790
|
+
committedTransaction,
|
|
791
|
+
this.initialization.maximumCommittedTransactionBytes,
|
|
792
|
+
"committed transaction",
|
|
793
|
+
);
|
|
794
|
+
segments.push(segment);
|
|
795
|
+
}
|
|
796
|
+
for (const transaction of transactions) {
|
|
797
|
+
const command = this.ownedSegment(
|
|
798
|
+
transaction.commandBytes,
|
|
799
|
+
this.initialization.maximumCommandBytes,
|
|
800
|
+
"pending command",
|
|
801
|
+
);
|
|
802
|
+
const intent = this.ownedSegment(
|
|
803
|
+
transaction.intentBytes,
|
|
804
|
+
EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
|
|
805
|
+
"pending intent",
|
|
806
|
+
);
|
|
807
|
+
segments.push(command, intent);
|
|
808
|
+
}
|
|
809
|
+
return segments;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
private measurePendingTransfer(
|
|
813
|
+
transactions: readonly EditableArtifactPendingTransaction[],
|
|
814
|
+
committedTransaction?: Uint8Array,
|
|
815
|
+
): number {
|
|
816
|
+
if (
|
|
817
|
+
!Array.isArray(transactions) ||
|
|
818
|
+
transactions.length > this.initialization.maximumPendingTransactions
|
|
819
|
+
) {
|
|
820
|
+
throw clientError("limit_exceeded", "pending transaction count exceeds its limit");
|
|
821
|
+
}
|
|
822
|
+
let pendingBytes = 0;
|
|
823
|
+
for (const transaction of transactions) {
|
|
824
|
+
const commandBytes = this.segmentByteLength(
|
|
825
|
+
transaction.commandBytes,
|
|
826
|
+
this.initialization.maximumCommandBytes,
|
|
827
|
+
"pending command",
|
|
828
|
+
);
|
|
829
|
+
const intentBytes = this.segmentByteLength(
|
|
830
|
+
transaction.intentBytes,
|
|
831
|
+
EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
|
|
832
|
+
"pending intent",
|
|
833
|
+
);
|
|
834
|
+
pendingBytes += commandBytes + intentBytes;
|
|
835
|
+
if (!Number.isSafeInteger(pendingBytes) || pendingBytes > this.maximumPendingAggregateBytes) {
|
|
836
|
+
throw clientError("limit_exceeded", "pending transfer exceeds its aggregate byte limit");
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const committedBytes =
|
|
840
|
+
committedTransaction === undefined
|
|
841
|
+
? 0
|
|
842
|
+
: this.segmentByteLength(
|
|
843
|
+
committedTransaction,
|
|
844
|
+
this.initialization.maximumCommittedTransactionBytes,
|
|
845
|
+
"committed transaction",
|
|
846
|
+
);
|
|
847
|
+
return pendingBytes + committedBytes;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
private async withAdmission<T>(transferBytes: number, operation: () => Promise<T>): Promise<T> {
|
|
851
|
+
if (this.disposed) throw clientError("disposed", "artifact Worker client is disposed");
|
|
852
|
+
if (
|
|
853
|
+
this.admittedRequests >= MAX_CLIENT_QUEUED_REQUESTS ||
|
|
854
|
+
!Number.isSafeInteger(this.admittedTransferBytes + transferBytes) ||
|
|
855
|
+
this.admittedTransferBytes + transferBytes > this.maximumQueuedTransferBytes
|
|
856
|
+
) {
|
|
857
|
+
throw clientError(
|
|
858
|
+
"client_busy",
|
|
859
|
+
"artifact Worker request queue exceeds its bounded capacity",
|
|
860
|
+
true,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
this.admittedRequests += 1;
|
|
864
|
+
this.admittedTransferBytes += transferBytes;
|
|
865
|
+
try {
|
|
866
|
+
return await operation();
|
|
867
|
+
} finally {
|
|
868
|
+
this.admittedRequests -= 1;
|
|
869
|
+
this.admittedTransferBytes -= transferBytes;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private segmentByteLength(bytes: Uint8Array, maximumBytes: number, label: string): number {
|
|
874
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
|
|
875
|
+
throw clientError("invalid_bytes", `${label} must be non-empty bytes`);
|
|
876
|
+
}
|
|
877
|
+
if (bytes.byteLength > maximumBytes) {
|
|
878
|
+
throw clientError("limit_exceeded", `${label} exceeds its byte limit`);
|
|
879
|
+
}
|
|
880
|
+
return bytes.byteLength;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private ownedSegment(bytes: Uint8Array, maximumBytes: number, label: string): ArrayBuffer {
|
|
884
|
+
this.segmentByteLength(bytes, maximumBytes, label);
|
|
885
|
+
return ownedTransferBuffer(bytes);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function nextRequestId(active: WorkerGeneration): number {
|
|
890
|
+
for (let attempts = 0; attempts < 0xffffffff; attempts += 1) {
|
|
891
|
+
const candidate = active.nextRequestId;
|
|
892
|
+
active.nextRequestId = candidate === 0xffffffff ? 1 : candidate + 1;
|
|
893
|
+
if (!active.pending.has(candidate)) return candidate;
|
|
894
|
+
}
|
|
895
|
+
throw clientError("request_exhausted", "artifact Worker request id space is exhausted");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function requireEmptyResult(result: RpcResult, label: string): void {
|
|
899
|
+
if (result.metadata.byteLength !== 0 || result.segments.length !== 0) {
|
|
900
|
+
throw clientError("invalid_response", `${label} response must be empty`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function canonicalUrl(input: string | URL, label: string): URL {
|
|
905
|
+
let url: URL;
|
|
906
|
+
try {
|
|
907
|
+
url = input instanceof URL ? new URL(input.href) : new URL(input);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
throw clientError("invalid_asset_url", `${label} must be an absolute URL`, false, error);
|
|
910
|
+
}
|
|
911
|
+
if (url.username || url.password || url.hash) {
|
|
912
|
+
throw clientError("invalid_asset_url", `${label} must not contain credentials or a fragment`);
|
|
913
|
+
}
|
|
914
|
+
return url;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function validateAssetPolicy(
|
|
918
|
+
urls: readonly URL[],
|
|
919
|
+
applicationOrigin: string | null,
|
|
920
|
+
configuredOrigins: readonly string[],
|
|
921
|
+
allowInsecureDevelopmentAssets: boolean,
|
|
922
|
+
): void {
|
|
923
|
+
const allowedOrigins = new Set<string>();
|
|
924
|
+
if (applicationOrigin !== null) allowedOrigins.add(applicationOrigin);
|
|
925
|
+
for (const originInput of configuredOrigins) {
|
|
926
|
+
const origin = canonicalUrl(originInput, "allowedAssetOrigins entry").origin;
|
|
927
|
+
allowedOrigins.add(origin);
|
|
928
|
+
}
|
|
929
|
+
for (const url of urls) {
|
|
930
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" && url.protocol !== "file:") {
|
|
931
|
+
throw clientError("invalid_asset_url", `unsupported artifact asset scheme: ${url.protocol}`);
|
|
932
|
+
}
|
|
933
|
+
const loopback =
|
|
934
|
+
url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
935
|
+
const secure = url.protocol === "https:" || (url.protocol === "http:" && loopback);
|
|
936
|
+
if (!secure && !allowInsecureDevelopmentAssets) {
|
|
937
|
+
throw clientError(
|
|
938
|
+
"insecure_asset_url",
|
|
939
|
+
"artifact Worker assets must use HTTPS (loopback HTTP allowed)",
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if (!allowedOrigins.has(url.origin)) {
|
|
943
|
+
throw clientError(
|
|
944
|
+
"untrusted_asset_origin",
|
|
945
|
+
`artifact Worker asset origin is not allowed: ${url.origin}`,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function resolveApplicationOrigin(input: string | URL | undefined): string | null {
|
|
952
|
+
if (input !== undefined) return canonicalUrl(input, "applicationOrigin").origin;
|
|
953
|
+
const location = globalThis.location;
|
|
954
|
+
if (location && typeof location.origin === "string" && location.origin !== "null") {
|
|
955
|
+
return canonicalUrl(location.origin, "application origin").origin;
|
|
956
|
+
}
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function boundedMaximum(value: number, hardMaximum: number, label: string): number {
|
|
961
|
+
positiveSafeInteger(value, label);
|
|
962
|
+
if (value > hardMaximum)
|
|
963
|
+
throw clientError("invalid_limit", `${label} exceeds its hard safety ceiling`);
|
|
964
|
+
return value;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function normalizeClientRpcLimits(
|
|
968
|
+
input: Readonly<ArtifactWorkerRpcLimits>,
|
|
969
|
+
): Readonly<ArtifactWorkerRpcLimits> {
|
|
970
|
+
const values = {
|
|
971
|
+
maxMetadataBytes: positiveSafeInteger(input.maxMetadataBytes, "rpcLimits.maxMetadataBytes"),
|
|
972
|
+
maxSegmentBytes: positiveSafeInteger(input.maxSegmentBytes, "rpcLimits.maxSegmentBytes"),
|
|
973
|
+
maxTotalSegmentBytes: positiveSafeInteger(
|
|
974
|
+
input.maxTotalSegmentBytes,
|
|
975
|
+
"rpcLimits.maxTotalSegmentBytes",
|
|
976
|
+
),
|
|
977
|
+
maxSegments: positiveSafeInteger(input.maxSegments, "rpcLimits.maxSegments"),
|
|
978
|
+
};
|
|
979
|
+
for (const key of Object.keys(values) as Array<keyof ArtifactWorkerRpcLimits>) {
|
|
980
|
+
if (values[key] > DEFAULT_ARTIFACT_WORKER_RPC_LIMITS[key]) {
|
|
981
|
+
throw clientError(
|
|
982
|
+
"invalid_limit",
|
|
983
|
+
`rpcLimits.${key} exceeds the dedicated Worker hard limit`,
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return Object.freeze(values);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function positiveSafeInteger(value: number, label: string): number {
|
|
991
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
992
|
+
throw clientError("invalid_number", `${label} must be a positive safe integer`);
|
|
993
|
+
}
|
|
994
|
+
return value;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function positiveVersion(value: number, label: string): number {
|
|
998
|
+
const version = positiveSafeInteger(value, label);
|
|
999
|
+
if (version > 65_535) throw clientError("invalid_number", `${label} exceeds uint16`);
|
|
1000
|
+
return version;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function boundedVersion(value: string, label: string): string {
|
|
1004
|
+
if (
|
|
1005
|
+
typeof value !== "string" ||
|
|
1006
|
+
value.length === 0 ||
|
|
1007
|
+
value.trim() !== value ||
|
|
1008
|
+
new TextEncoder().encode(value).byteLength > 512
|
|
1009
|
+
) {
|
|
1010
|
+
throw clientError("invalid_runtime_identity", `${label} is malformed`);
|
|
1011
|
+
}
|
|
1012
|
+
return value;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function normalizeViewportQuery(
|
|
1016
|
+
query: EditableSpreadsheetViewportQuery,
|
|
1017
|
+
): SpreadsheetArtifactViewportQuery {
|
|
1018
|
+
if (!query || typeof query !== "object") {
|
|
1019
|
+
throw clientError("invalid_query", "spreadsheet viewport query must be an object");
|
|
1020
|
+
}
|
|
1021
|
+
const sheetId = spreadsheetSheetId(requireStableId(query.sheetId, "query.sheetId"));
|
|
1022
|
+
const startRow = uint32(query.startRow, "query.startRow");
|
|
1023
|
+
const startColumn = uint32(query.startColumn, "query.startColumn");
|
|
1024
|
+
const rowCount = positiveUint32(query.rowCount, "query.rowCount");
|
|
1025
|
+
const columnCount = positiveUint32(query.columnCount, "query.columnCount");
|
|
1026
|
+
if (startRow + rowCount - 1 > UINT32_MAX || startColumn + columnCount - 1 > UINT32_MAX) {
|
|
1027
|
+
throw clientError("invalid_query", "spreadsheet viewport exceeds uint32 coordinates");
|
|
1028
|
+
}
|
|
1029
|
+
const area = rowCount * columnCount;
|
|
1030
|
+
if (!Number.isSafeInteger(area) || area > MAX_VIEWPORT_AREA) {
|
|
1031
|
+
throw clientError("limit_exceeded", `spreadsheet viewport exceeds ${MAX_VIEWPORT_AREA} cells`);
|
|
1032
|
+
}
|
|
1033
|
+
const maxCells = positiveUint32(
|
|
1034
|
+
query.maxCells ?? SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS,
|
|
1035
|
+
"query.maxCells",
|
|
1036
|
+
);
|
|
1037
|
+
const maxBytes = positiveUint32(
|
|
1038
|
+
query.maxBytes ?? SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
|
|
1039
|
+
"query.maxBytes",
|
|
1040
|
+
);
|
|
1041
|
+
if (maxCells > SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS) {
|
|
1042
|
+
throw clientError("limit_exceeded", "spreadsheet viewport maxCells exceeds its hard bound");
|
|
1043
|
+
}
|
|
1044
|
+
if (maxBytes > SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES) {
|
|
1045
|
+
throw clientError("limit_exceeded", "spreadsheet viewport maxBytes exceeds its hard bound");
|
|
1046
|
+
}
|
|
1047
|
+
return Object.freeze({
|
|
1048
|
+
sheetId,
|
|
1049
|
+
startRow,
|
|
1050
|
+
startColumn,
|
|
1051
|
+
rowCount,
|
|
1052
|
+
columnCount,
|
|
1053
|
+
maxCells,
|
|
1054
|
+
maxBytes,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function normalizeMetadataQuery(
|
|
1059
|
+
query: EditableSpreadsheetMetadataQuery,
|
|
1060
|
+
): Required<EditableSpreadsheetMetadataQuery> {
|
|
1061
|
+
if (!query || typeof query !== "object") {
|
|
1062
|
+
throw clientError("invalid_query", "spreadsheet metadata query must be an object");
|
|
1063
|
+
}
|
|
1064
|
+
const maxSheets = positiveUint32(query.maxSheets ?? 4_096, "query.maxSheets");
|
|
1065
|
+
const maxBytes = positiveUint32(
|
|
1066
|
+
query.maxBytes ?? SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
|
|
1067
|
+
"query.maxBytes",
|
|
1068
|
+
);
|
|
1069
|
+
if (maxSheets > SPREADSHEET_ARTIFACT_METADATA_MAX_SHEETS) {
|
|
1070
|
+
throw clientError("limit_exceeded", "spreadsheet metadata maxSheets exceeds its hard bound");
|
|
1071
|
+
}
|
|
1072
|
+
if (maxBytes > SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES) {
|
|
1073
|
+
throw clientError("limit_exceeded", "spreadsheet metadata maxBytes exceeds its hard bound");
|
|
1074
|
+
}
|
|
1075
|
+
return Object.freeze({ maxSheets, maxBytes });
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function assertPresentationProjectionMatchesQuery(
|
|
1079
|
+
query: EditablePresentationQuery,
|
|
1080
|
+
response: EditablePresentationProjection,
|
|
1081
|
+
): void {
|
|
1082
|
+
if (query.kind !== response.kind) {
|
|
1083
|
+
throw clientError("invalid_response", "presentation projection kind does not match its query");
|
|
1084
|
+
}
|
|
1085
|
+
if (query.kind === "resolved-slide" && response.kind === "resolved-slide") {
|
|
1086
|
+
if (response.slideId !== query.slideId || response.nodes.length > query.maxNodes)
|
|
1087
|
+
throw clientError(
|
|
1088
|
+
"invalid_response",
|
|
1089
|
+
"resolved presentation projection does not match its query",
|
|
1090
|
+
);
|
|
1091
|
+
} else if (query.kind === "slide-catalog" && response.kind === "slide-catalog") {
|
|
1092
|
+
if (
|
|
1093
|
+
response.startSlide !== query.startSlide ||
|
|
1094
|
+
response.slides.length > query.maxSlides ||
|
|
1095
|
+
response.projectedTextBytes > query.maxTextBytes
|
|
1096
|
+
)
|
|
1097
|
+
throw clientError("invalid_response", "presentation slide catalog does not match its query");
|
|
1098
|
+
} else if (query.kind === "editor-slide" && response.kind === "editor-slide") {
|
|
1099
|
+
if (
|
|
1100
|
+
response.slide.id !== query.slideId ||
|
|
1101
|
+
response.nodes.length > query.maxNodes ||
|
|
1102
|
+
response.projectedTextBytes > query.maxTextBytes
|
|
1103
|
+
)
|
|
1104
|
+
throw clientError("invalid_response", "presentation editor slide does not match its query");
|
|
1105
|
+
} else if (
|
|
1106
|
+
(query.kind === "viewport" || query.kind === "hit-test") &&
|
|
1107
|
+
(response.kind === "viewport" || response.kind === "hit-test") &&
|
|
1108
|
+
response.nodes.length > query.maxNodes
|
|
1109
|
+
) {
|
|
1110
|
+
throw clientError("invalid_response", "presentation projection exceeds its requested nodes");
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function uint32(value: number, label: string): number {
|
|
1115
|
+
if (!Number.isInteger(value) || value < 0 || value > UINT32_MAX) {
|
|
1116
|
+
throw clientError("invalid_query", `${label} must be a uint32`);
|
|
1117
|
+
}
|
|
1118
|
+
return value;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function positiveUint32(value: number, label: string): number {
|
|
1122
|
+
const output = uint32(value, label);
|
|
1123
|
+
if (output === 0) throw clientError("invalid_query", `${label} must be positive`);
|
|
1124
|
+
return output;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function requireStableId(value: string, label: string): string {
|
|
1128
|
+
if (!/^[0-9a-f]{32}$/u.test(value) || /^0+$/u.test(value)) {
|
|
1129
|
+
throw clientError(
|
|
1130
|
+
"invalid_query",
|
|
1131
|
+
`${label} must be nonzero fixed-width lowercase hexadecimal text`,
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return value;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function requireModality(value: unknown): EditableArtifactModality {
|
|
1138
|
+
if (value !== "document" && value !== "spreadsheet" && value !== "presentation") {
|
|
1139
|
+
throw clientError("invalid_modality", "artifact modality is invalid");
|
|
1140
|
+
}
|
|
1141
|
+
return value;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function defaultWorkerFactory(
|
|
1145
|
+
url: URL,
|
|
1146
|
+
options: { type: "module"; name: string },
|
|
1147
|
+
): ArtifactWorkerClientEndpoint {
|
|
1148
|
+
if (typeof globalThis.Worker !== "function") {
|
|
1149
|
+
throw clientError(
|
|
1150
|
+
"worker_unavailable",
|
|
1151
|
+
"browser artifact Worker is unavailable in this SSR/runtime environment",
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
return new globalThis.Worker(url, options) as unknown as ArtifactWorkerClientEndpoint;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function clientError(
|
|
1158
|
+
code: string,
|
|
1159
|
+
message: string,
|
|
1160
|
+
retryable = false,
|
|
1161
|
+
cause?: unknown,
|
|
1162
|
+
): ArtifactWorkerClientError {
|
|
1163
|
+
return new ArtifactWorkerClientError(code, message, {
|
|
1164
|
+
retryable,
|
|
1165
|
+
...(cause === undefined ? {} : { cause }),
|
|
1166
|
+
});
|
|
1167
|
+
}
|