@opengeni/sdk 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-QKV5OCLJ.js → chunk-MBGBLVUV.js} +346 -72
- package/dist/chunk-MBGBLVUV.js.map +1 -0
- package/dist/client.d.ts +27 -5
- package/dist/core.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/stream.d.ts +15 -0
- package/dist/transcription.d.ts +2 -2
- package/dist/types.d.ts +80 -10
- package/dist/workspace-control-stream.d.ts +1 -1
- package/package.json +1 -1
- package/src/client.ts +344 -60
- package/src/index.ts +9 -0
- package/src/stream.ts +102 -4
- package/src/transcription.ts +2 -2
- package/src/types.ts +89 -7
- package/src/workspace-control-stream.ts +15 -5
- package/dist/chunk-QKV5OCLJ.js.map +0 -1
package/src/client.ts
CHANGED
|
@@ -101,6 +101,8 @@ import type {
|
|
|
101
101
|
MachinesResponse,
|
|
102
102
|
MetricSample,
|
|
103
103
|
MachineMetricsSeriesResponse,
|
|
104
|
+
RemoveEnrollmentRequest,
|
|
105
|
+
RemoveEnrollmentResponse,
|
|
104
106
|
// Bring-your-own-compute: the user-authenticated active-sandbox swap (M7).
|
|
105
107
|
SwapActiveSandboxRequest,
|
|
106
108
|
SwapActiveSandboxResponse,
|
|
@@ -108,6 +110,8 @@ import type {
|
|
|
108
110
|
PackInstallation,
|
|
109
111
|
LatencyMode,
|
|
110
112
|
ReasoningEffort,
|
|
113
|
+
RetainedScreenshotDownload,
|
|
114
|
+
RetainedScreenshotDownloadOptions,
|
|
111
115
|
RetainedArtifactContent,
|
|
112
116
|
RetainedArtifactContentOptions,
|
|
113
117
|
RetainedArtifactMetadata,
|
|
@@ -159,6 +163,8 @@ import type {
|
|
|
159
163
|
// Channel-A structured services (P4.4).
|
|
160
164
|
FsListRequest,
|
|
161
165
|
FsListResponse,
|
|
166
|
+
FsListBatchRequest,
|
|
167
|
+
FsListBatchResponse,
|
|
162
168
|
FsReadRequest,
|
|
163
169
|
FsReadResponse,
|
|
164
170
|
FsWriteRequest,
|
|
@@ -173,6 +179,8 @@ import type {
|
|
|
173
179
|
GitStatusResponse,
|
|
174
180
|
GitDiffRequest,
|
|
175
181
|
GitDiffResponse,
|
|
182
|
+
GitReadBatchRequest,
|
|
183
|
+
GitReadBatchResponse,
|
|
176
184
|
GitLogRequest,
|
|
177
185
|
GitLogResponse,
|
|
178
186
|
GitShowRequest,
|
|
@@ -206,6 +214,7 @@ import type {
|
|
|
206
214
|
SetWorkspaceDefaultRigRequest,
|
|
207
215
|
UploadFileInput,
|
|
208
216
|
VariableSet,
|
|
217
|
+
VariableSetSecret,
|
|
209
218
|
VariableSetVariableMetadata,
|
|
210
219
|
Rig,
|
|
211
220
|
RigVersion,
|
|
@@ -264,6 +273,7 @@ import {
|
|
|
264
273
|
OPENGENI_API_CONTRACT_HEADER,
|
|
265
274
|
OPENGENI_API_CONTRACT_REVISION,
|
|
266
275
|
OPENGENI_CORRELATION_HEADER,
|
|
276
|
+
COMPUTER_SCREENSHOT_MAX_BYTES,
|
|
267
277
|
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
268
278
|
} from "./types";
|
|
269
279
|
|
|
@@ -363,6 +373,8 @@ export class OpenGeniClient {
|
|
|
363
373
|
private readonly baseUrl: string;
|
|
364
374
|
private readonly options: OpenGeniClientOptions;
|
|
365
375
|
private readonly fetchImpl: FetchLike;
|
|
376
|
+
private readonly readInFlight = new Map<string, Promise<unknown>>();
|
|
377
|
+
private readonly readTrailing = new Map<string, Promise<unknown>>();
|
|
366
378
|
|
|
367
379
|
constructor(options: OpenGeniClientOptions) {
|
|
368
380
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
@@ -385,8 +397,12 @@ export class OpenGeniClient {
|
|
|
385
397
|
input.audio instanceof File
|
|
386
398
|
? input.audio
|
|
387
399
|
: input.audio instanceof Uint8Array
|
|
388
|
-
? new File([Uint8Array.from(input.audio)], filename, {
|
|
389
|
-
|
|
400
|
+
? new File([Uint8Array.from(input.audio)], filename, {
|
|
401
|
+
type: input.mimeType,
|
|
402
|
+
})
|
|
403
|
+
: new File([input.audio], filename, {
|
|
404
|
+
type: input.mimeType || input.audio.type,
|
|
405
|
+
});
|
|
390
406
|
form.append("audio", audio, filename);
|
|
391
407
|
form.append("mimeType", input.mimeType);
|
|
392
408
|
if (input.durationSeconds !== undefined) {
|
|
@@ -396,7 +412,10 @@ export class OpenGeniClient {
|
|
|
396
412
|
try {
|
|
397
413
|
response = await this.fetchImpl(this.url(`/v1/workspaces/${workspaceId}/transcriptions`), {
|
|
398
414
|
method: "POST",
|
|
399
|
-
headers: {
|
|
415
|
+
headers: {
|
|
416
|
+
...this.headers(correlationId),
|
|
417
|
+
Accept: "application/json",
|
|
418
|
+
},
|
|
400
419
|
body: form,
|
|
401
420
|
...(input.signal ? { signal: input.signal } : {}),
|
|
402
421
|
});
|
|
@@ -405,7 +424,11 @@ export class OpenGeniClient {
|
|
|
405
424
|
throw mutationTransportError(correlationId);
|
|
406
425
|
}
|
|
407
426
|
assertApiContractResponse(response);
|
|
408
|
-
if (!response.ok)
|
|
427
|
+
if (!response.ok)
|
|
428
|
+
throw await apiErrorFromResponse(response, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
correlationId,
|
|
431
|
+
});
|
|
409
432
|
await assertJsonResponse(response, { method: "POST", correlationId });
|
|
410
433
|
let body: unknown;
|
|
411
434
|
try {
|
|
@@ -509,7 +532,11 @@ export class OpenGeniClient {
|
|
|
509
532
|
throw mutationTransportError(correlationId);
|
|
510
533
|
}
|
|
511
534
|
assertApiContractResponse(response);
|
|
512
|
-
if (!response.ok)
|
|
535
|
+
if (!response.ok)
|
|
536
|
+
throw await apiErrorFromResponse(response, {
|
|
537
|
+
method: "PUT",
|
|
538
|
+
correlationId,
|
|
539
|
+
});
|
|
513
540
|
await assertJsonResponse(response, { method: "PUT", correlationId });
|
|
514
541
|
const body = await response.json().catch(() => null);
|
|
515
542
|
if (!isUploadTranscriptionRecordingChunkResponse(body)) {
|
|
@@ -603,11 +630,13 @@ export class OpenGeniClient {
|
|
|
603
630
|
);
|
|
604
631
|
}
|
|
605
632
|
|
|
606
|
-
async getSession(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
633
|
+
async getSession(
|
|
634
|
+
workspaceId: string,
|
|
635
|
+
sessionId: string,
|
|
636
|
+
options: { fresh?: boolean } = {},
|
|
637
|
+
): Promise<Session> {
|
|
638
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}`;
|
|
639
|
+
return await this.singleFlightRead(path, () => this.requestJson<Session>("GET", path), options);
|
|
611
640
|
}
|
|
612
641
|
|
|
613
642
|
async updateSession(
|
|
@@ -754,12 +783,40 @@ export class OpenGeniClient {
|
|
|
754
783
|
}
|
|
755
784
|
|
|
756
785
|
async getSessionLineage(workspaceId: string, sessionId: string): Promise<SessionLineageResponse> {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
786
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
|
|
787
|
+
return await this.singleFlightRead(path, () =>
|
|
788
|
+
this.requestJson<SessionLineageResponse>("GET", path),
|
|
760
789
|
);
|
|
761
790
|
}
|
|
762
791
|
|
|
792
|
+
private singleFlightRead<T>(
|
|
793
|
+
key: string,
|
|
794
|
+
read: () => Promise<T>,
|
|
795
|
+
options: { fresh?: boolean } = {},
|
|
796
|
+
): Promise<T> {
|
|
797
|
+
const existing = this.readInFlight.get(key);
|
|
798
|
+
if (existing) {
|
|
799
|
+
if (!options.fresh) return existing as Promise<T>;
|
|
800
|
+
const queued = this.readTrailing.get(key);
|
|
801
|
+
if (queued) return queued as Promise<T>;
|
|
802
|
+
const trailing = existing.then(
|
|
803
|
+
() => this.singleFlightRead(key, read),
|
|
804
|
+
() => this.singleFlightRead(key, read),
|
|
805
|
+
);
|
|
806
|
+
this.readTrailing.set(key, trailing);
|
|
807
|
+
const clear = () => {
|
|
808
|
+
if (this.readTrailing.get(key) === trailing) this.readTrailing.delete(key);
|
|
809
|
+
};
|
|
810
|
+
void trailing.then(clear, clear);
|
|
811
|
+
return trailing;
|
|
812
|
+
}
|
|
813
|
+
const promise = read().finally(() => {
|
|
814
|
+
if (this.readInFlight.get(key) === promise) this.readInFlight.delete(key);
|
|
815
|
+
});
|
|
816
|
+
this.readInFlight.set(key, promise);
|
|
817
|
+
return promise;
|
|
818
|
+
}
|
|
819
|
+
|
|
763
820
|
/** Negotiate one server-mediated connected-Codex GPT-Live V3 WebRTC call. */
|
|
764
821
|
async negotiateCodexRealtimeWebrtc(
|
|
765
822
|
workspaceId: string,
|
|
@@ -883,7 +940,9 @@ export class OpenGeniClient {
|
|
|
883
940
|
|
|
884
941
|
/** Newest turn that durably emitted `turn.started`, or null before any admission. */
|
|
885
942
|
async getLatestStartedTurn(workspaceId: string, sessionId: string): Promise<SessionTurn | null> {
|
|
886
|
-
const turns = await this.listTurns(workspaceId, sessionId, {
|
|
943
|
+
const turns = await this.listTurns(workspaceId, sessionId, {
|
|
944
|
+
latestStarted: true,
|
|
945
|
+
});
|
|
887
946
|
return turns[0] ?? null;
|
|
888
947
|
}
|
|
889
948
|
|
|
@@ -928,6 +987,24 @@ export class OpenGeniClient {
|
|
|
928
987
|
return response.samples;
|
|
929
988
|
}
|
|
930
989
|
|
|
990
|
+
/**
|
|
991
|
+
* Remove one connected self-hosted machine enrollment. The control-plane
|
|
992
|
+
* operation works while the agent is offline, revokes future reconnects,
|
|
993
|
+
* retains history, and returns a typed blocker when active route/lease or
|
|
994
|
+
* recovery dependencies make removal unsafe. `idempotencyKey` is replay-safe.
|
|
995
|
+
*/
|
|
996
|
+
async removeEnrollment(
|
|
997
|
+
workspaceId: string,
|
|
998
|
+
enrollmentId: string,
|
|
999
|
+
request: RemoveEnrollmentRequest = {},
|
|
1000
|
+
): Promise<RemoveEnrollmentResponse> {
|
|
1001
|
+
return await this.requestJson<RemoveEnrollmentResponse>(
|
|
1002
|
+
"POST",
|
|
1003
|
+
`/v1/workspaces/${workspaceId}/enrollments/${enrollmentId}/revoke`,
|
|
1004
|
+
request,
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
931
1008
|
// --- Self-hosted enrollment UX (design 11) --------------------------------
|
|
932
1009
|
|
|
933
1010
|
/**
|
|
@@ -1350,9 +1427,9 @@ export class OpenGeniClient {
|
|
|
1350
1427
|
// --- Turn queue ------------------------------------------------------------
|
|
1351
1428
|
|
|
1352
1429
|
async getQueue(workspaceId: string, sessionId: string): Promise<SessionQueueSnapshot> {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1430
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`;
|
|
1431
|
+
return await this.singleFlightRead(path, () =>
|
|
1432
|
+
this.requestJson<SessionQueueSnapshot>("GET", path),
|
|
1356
1433
|
);
|
|
1357
1434
|
}
|
|
1358
1435
|
|
|
@@ -1469,7 +1546,11 @@ export class OpenGeniClient {
|
|
|
1469
1546
|
async cancelSession(
|
|
1470
1547
|
workspaceId: string,
|
|
1471
1548
|
sessionId: string,
|
|
1472
|
-
options: {
|
|
1549
|
+
options: {
|
|
1550
|
+
reason?: string;
|
|
1551
|
+
clientEventId?: string;
|
|
1552
|
+
expectedControlEtag?: string;
|
|
1553
|
+
} = {},
|
|
1473
1554
|
): Promise<SessionControlResponse> {
|
|
1474
1555
|
return await this.controlSession(workspaceId, sessionId, {
|
|
1475
1556
|
action: "cancel",
|
|
@@ -1614,10 +1695,8 @@ export class OpenGeniClient {
|
|
|
1614
1695
|
|
|
1615
1696
|
/** The session's goal. 404s when the session never had one. */
|
|
1616
1697
|
async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
|
|
1620
|
-
);
|
|
1698
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
|
|
1699
|
+
return await this.singleFlightRead(path, () => this.requestJson<SessionGoal>("GET", path));
|
|
1621
1700
|
}
|
|
1622
1701
|
|
|
1623
1702
|
async updateGoal(
|
|
@@ -1703,6 +1782,22 @@ export class OpenGeniClient {
|
|
|
1703
1782
|
);
|
|
1704
1783
|
}
|
|
1705
1784
|
|
|
1785
|
+
/** FileSystem: hydrate several independent directories behind one sandbox lease. */
|
|
1786
|
+
async fsListBatch(
|
|
1787
|
+
workspaceId: string,
|
|
1788
|
+
sessionId: string,
|
|
1789
|
+
request: FsListBatchRequest,
|
|
1790
|
+
options: OpenGeniRequestOptions = {},
|
|
1791
|
+
): Promise<FsListBatchResponse> {
|
|
1792
|
+
return await this.requestJson<FsListBatchResponse>(
|
|
1793
|
+
"POST",
|
|
1794
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list-batch`,
|
|
1795
|
+
request,
|
|
1796
|
+
{},
|
|
1797
|
+
options,
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1706
1801
|
/** FileSystem: read a file (text or base64; binary-safe, size-capped). */
|
|
1707
1802
|
async fsRead(
|
|
1708
1803
|
workspaceId: string,
|
|
@@ -1803,6 +1898,22 @@ export class OpenGeniClient {
|
|
|
1803
1898
|
);
|
|
1804
1899
|
}
|
|
1805
1900
|
|
|
1901
|
+
/** Git: read status and optional diffs for several repositories behind one sandbox lease. */
|
|
1902
|
+
async gitReadBatch(
|
|
1903
|
+
workspaceId: string,
|
|
1904
|
+
sessionId: string,
|
|
1905
|
+
request: GitReadBatchRequest,
|
|
1906
|
+
options: OpenGeniRequestOptions = {},
|
|
1907
|
+
): Promise<GitReadBatchResponse> {
|
|
1908
|
+
return await this.requestJson<GitReadBatchResponse>(
|
|
1909
|
+
"POST",
|
|
1910
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/read-batch`,
|
|
1911
|
+
request,
|
|
1912
|
+
{},
|
|
1913
|
+
options,
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1806
1917
|
/** Git: commit log. */
|
|
1807
1918
|
async gitLog(
|
|
1808
1919
|
workspaceId: string,
|
|
@@ -2484,7 +2595,8 @@ export class OpenGeniClient {
|
|
|
2484
2595
|
}
|
|
2485
2596
|
|
|
2486
2597
|
// --- VariableSets --------------------------------------------------------------
|
|
2487
|
-
//
|
|
2598
|
+
// Generic reads return name/version metadata only. Plaintext uses one
|
|
2599
|
+
// dedicated permissioned endpoint.
|
|
2488
2600
|
|
|
2489
2601
|
async listVariableSets(workspaceId: string): Promise<VariableSet[]> {
|
|
2490
2602
|
return await this.requestJson<VariableSet[]>(
|
|
@@ -2511,6 +2623,17 @@ export class OpenGeniClient {
|
|
|
2511
2623
|
);
|
|
2512
2624
|
}
|
|
2513
2625
|
|
|
2626
|
+
async getVariableSetVariable(
|
|
2627
|
+
workspaceId: string,
|
|
2628
|
+
variableSetId: string,
|
|
2629
|
+
name: string,
|
|
2630
|
+
): Promise<VariableSetSecret> {
|
|
2631
|
+
return await this.requestJson<VariableSetSecret>(
|
|
2632
|
+
"GET",
|
|
2633
|
+
`/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2514
2637
|
async updateVariableSet(
|
|
2515
2638
|
workspaceId: string,
|
|
2516
2639
|
variableSetId: string,
|
|
@@ -2530,7 +2653,7 @@ export class OpenGeniClient {
|
|
|
2530
2653
|
);
|
|
2531
2654
|
}
|
|
2532
2655
|
|
|
2533
|
-
/** Create or rotate a variable.
|
|
2656
|
+
/** Create or rotate a variable. Generic reads never return its value. */
|
|
2534
2657
|
async setVariableSetVariable(
|
|
2535
2658
|
workspaceId: string,
|
|
2536
2659
|
variableSetId: string,
|
|
@@ -2726,19 +2849,29 @@ export class OpenGeniClient {
|
|
|
2726
2849
|
async beginFileUpload(
|
|
2727
2850
|
workspaceId: string,
|
|
2728
2851
|
request: CreateFileUploadRequest,
|
|
2852
|
+
options: OpenGeniRequestOptions = {},
|
|
2729
2853
|
): Promise<CreateFileUploadResponse> {
|
|
2730
2854
|
return await this.requestJson<CreateFileUploadResponse>(
|
|
2731
2855
|
"POST",
|
|
2732
2856
|
`/v1/workspaces/${workspaceId}/files/uploads`,
|
|
2733
2857
|
request,
|
|
2858
|
+
{},
|
|
2859
|
+
options,
|
|
2734
2860
|
);
|
|
2735
2861
|
}
|
|
2736
2862
|
|
|
2737
2863
|
/** Step 3 of the upload flow: server verifies the object and marks it ready. */
|
|
2738
|
-
async completeFileUpload(
|
|
2864
|
+
async completeFileUpload(
|
|
2865
|
+
workspaceId: string,
|
|
2866
|
+
uploadId: string,
|
|
2867
|
+
options: OpenGeniRequestOptions = {},
|
|
2868
|
+
): Promise<FileAsset> {
|
|
2739
2869
|
const response = await this.requestJson<CompleteFileUploadResponse>(
|
|
2740
2870
|
"POST",
|
|
2741
2871
|
`/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`,
|
|
2872
|
+
undefined,
|
|
2873
|
+
{},
|
|
2874
|
+
options,
|
|
2742
2875
|
);
|
|
2743
2876
|
return response.file;
|
|
2744
2877
|
}
|
|
@@ -2749,6 +2882,30 @@ export class OpenGeniClient {
|
|
|
2749
2882
|
* -> complete. Returns the ready `FileAsset`.
|
|
2750
2883
|
*/
|
|
2751
2884
|
async uploadFile(workspaceId: string, input: UploadFileInput): Promise<FileAsset> {
|
|
2885
|
+
if (
|
|
2886
|
+
input.timeoutMs !== undefined &&
|
|
2887
|
+
(!Number.isFinite(input.timeoutMs) || input.timeoutMs <= 0)
|
|
2888
|
+
) {
|
|
2889
|
+
throw new Error("File upload timeout must be a positive number");
|
|
2890
|
+
}
|
|
2891
|
+
const withTimeout = async <T>(
|
|
2892
|
+
timeoutMs: number,
|
|
2893
|
+
operation: (signal: AbortSignal) => Promise<T>,
|
|
2894
|
+
): Promise<T> => {
|
|
2895
|
+
const controller = new AbortController();
|
|
2896
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
2897
|
+
const timedOut = new Promise<never>((_resolve, reject) => {
|
|
2898
|
+
timeout = setTimeout(() => {
|
|
2899
|
+
controller.abort();
|
|
2900
|
+
reject(new Error("File upload timed out. Retry the upload."));
|
|
2901
|
+
}, timeoutMs);
|
|
2902
|
+
});
|
|
2903
|
+
try {
|
|
2904
|
+
return await Promise.race([operation(controller.signal), timedOut]);
|
|
2905
|
+
} finally {
|
|
2906
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2752
2909
|
// Snapshot mutable inputs before hashing so the digest always describes the
|
|
2753
2910
|
// exact bytes later sent to object storage. Copy Uint8Array views into a
|
|
2754
2911
|
// Blob so byte offsets/shared buffers can't leak surrounding bytes.
|
|
@@ -2765,32 +2922,52 @@ export class OpenGeniClient {
|
|
|
2765
2922
|
? body.size
|
|
2766
2923
|
: body.byteLength;
|
|
2767
2924
|
const sha256 = input.sha256 ?? (await sha256ForUpload(body));
|
|
2768
|
-
const upload = await
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2925
|
+
const upload = await withTimeout(
|
|
2926
|
+
30_000,
|
|
2927
|
+
async (signal) =>
|
|
2928
|
+
await this.beginFileUpload(
|
|
2929
|
+
workspaceId,
|
|
2930
|
+
{
|
|
2931
|
+
filename: input.filename,
|
|
2932
|
+
contentType: input.contentType,
|
|
2933
|
+
sizeBytes,
|
|
2934
|
+
sha256,
|
|
2935
|
+
},
|
|
2936
|
+
{ signal },
|
|
2937
|
+
),
|
|
2938
|
+
);
|
|
2939
|
+
// Give large valid uploads enough time at a conservative 256 KiB/s while
|
|
2940
|
+
// still bounding an object-storage request that never settles.
|
|
2941
|
+
const transferTimeoutMs =
|
|
2942
|
+
input.timeoutMs ?? Math.max(120_000, Math.ceil(sizeBytes / (256 * 1024)) * 1_000);
|
|
2943
|
+
const putResponse = await withTimeout(
|
|
2944
|
+
transferTimeoutMs,
|
|
2945
|
+
async (signal) =>
|
|
2946
|
+
await this.fetchImpl(upload.putUrl, {
|
|
2947
|
+
method: "PUT",
|
|
2948
|
+
// Signed object-storage URLs carry their own short-lived authority.
|
|
2949
|
+
// Browser cookies and HTTP auth must never accompany this cross-origin
|
|
2950
|
+
// request: credentialed fetches are incompatible with wildcard CORS and
|
|
2951
|
+
// can leak ambient credentials to a caller-selected storage endpoint.
|
|
2952
|
+
credentials: "omit",
|
|
2953
|
+
// The backend's requiredHeaders already carry the canonical lowercase
|
|
2954
|
+
// `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
|
|
2955
|
+
// a `Content-Type` key here: WHATWG Headers treats the two casings as the
|
|
2956
|
+
// same header and comma-joins their values (e.g. "text/plain, text/plain"),
|
|
2957
|
+
// which the object store persists verbatim and COMPLETE then rejects (422),
|
|
2958
|
+
// and which breaks S3's presigned-URL signature.
|
|
2959
|
+
headers: { ...upload.requiredHeaders },
|
|
2960
|
+
body,
|
|
2961
|
+
signal,
|
|
2962
|
+
}),
|
|
2963
|
+
);
|
|
2790
2964
|
if (!putResponse.ok) {
|
|
2791
2965
|
throw await apiErrorFromResponse(putResponse, { method: "PUT" });
|
|
2792
2966
|
}
|
|
2793
|
-
return await
|
|
2967
|
+
return await withTimeout(
|
|
2968
|
+
30_000,
|
|
2969
|
+
async (signal) => await this.completeFileUpload(workspaceId, upload.uploadId, { signal }),
|
|
2970
|
+
);
|
|
2794
2971
|
}
|
|
2795
2972
|
|
|
2796
2973
|
async getFile(workspaceId: string, fileId: string): Promise<FileAsset> {
|
|
@@ -2819,23 +2996,119 @@ export class OpenGeniClient {
|
|
|
2819
2996
|
workspaceId: string,
|
|
2820
2997
|
artifactId: string,
|
|
2821
2998
|
options: RetainedArtifactContentOptions = {},
|
|
2999
|
+
): Promise<RetainedArtifactContent> {
|
|
3000
|
+
return await this.getRetainedArtifactContentAtPath(
|
|
3001
|
+
`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`,
|
|
3002
|
+
options,
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
async getSessionRetainedArtifact(
|
|
3007
|
+
workspaceId: string,
|
|
3008
|
+
sessionId: string,
|
|
3009
|
+
artifactId: string,
|
|
3010
|
+
): Promise<RetainedArtifactMetadata> {
|
|
3011
|
+
return await this.requestJson<RetainedArtifactMetadata>(
|
|
3012
|
+
"GET",
|
|
3013
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}`,
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
async getSessionRetainedArtifactContent(
|
|
3018
|
+
workspaceId: string,
|
|
3019
|
+
sessionId: string,
|
|
3020
|
+
artifactId: string,
|
|
3021
|
+
options: RetainedArtifactContentOptions = {},
|
|
3022
|
+
): Promise<RetainedArtifactContent> {
|
|
3023
|
+
return await this.getRetainedArtifactContentAtPath(
|
|
3024
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}/content`,
|
|
3025
|
+
options,
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
/** Assemble one retained screenshot from bounded authenticated API ranges. */
|
|
3030
|
+
async downloadRetainedScreenshot(
|
|
3031
|
+
workspaceId: string,
|
|
3032
|
+
sessionId: string,
|
|
3033
|
+
artifactId: string,
|
|
3034
|
+
options: RetainedScreenshotDownloadOptions = {},
|
|
3035
|
+
): Promise<RetainedScreenshotDownload> {
|
|
3036
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
3037
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 3) {
|
|
3038
|
+
throw new RangeError("retained screenshot maxRetries must be an integer from 0 to 3");
|
|
3039
|
+
}
|
|
3040
|
+
const metadata = await this.getSessionRetainedArtifact(workspaceId, sessionId, artifactId);
|
|
3041
|
+
if (!metadata.available) return { metadata, bytes: null };
|
|
3042
|
+
if (
|
|
3043
|
+
metadata.kind !== "computer_screenshot" ||
|
|
3044
|
+
metadata.contentType !== "image/png" ||
|
|
3045
|
+
!metadata.dimensions ||
|
|
3046
|
+
metadata.originalBytes <= 0 ||
|
|
3047
|
+
metadata.originalBytes > COMPUTER_SCREENSHOT_MAX_BYTES
|
|
3048
|
+
) {
|
|
3049
|
+
throw new OpenGeniApiError(502, "retained screenshot metadata is invalid");
|
|
3050
|
+
}
|
|
3051
|
+
const bytes = new Uint8Array(metadata.originalBytes);
|
|
3052
|
+
const pageBytes = Math.min(metadata.retrieval.maxRangeBytes, RETAINED_OUTPUT_MAX_PAGE_BYTES);
|
|
3053
|
+
if (!Number.isSafeInteger(pageBytes) || pageBytes <= 0) {
|
|
3054
|
+
throw new OpenGeniApiError(502, "retained screenshot range metadata is invalid");
|
|
3055
|
+
}
|
|
3056
|
+
for (let start = 0; start < bytes.byteLength; start += pageBytes) {
|
|
3057
|
+
options.signal?.throwIfAborted();
|
|
3058
|
+
const end = Math.min(start + pageBytes, bytes.byteLength) - 1;
|
|
3059
|
+
let page: RetainedArtifactContent | null = null;
|
|
3060
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
3061
|
+
try {
|
|
3062
|
+
page = await this.getSessionRetainedArtifactContent(workspaceId, sessionId, artifactId, {
|
|
3063
|
+
range: `bytes=${start}-${end}`,
|
|
3064
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
3065
|
+
});
|
|
3066
|
+
break;
|
|
3067
|
+
} catch (error) {
|
|
3068
|
+
options.signal?.throwIfAborted();
|
|
3069
|
+
if (
|
|
3070
|
+
attempt >= maxRetries ||
|
|
3071
|
+
(error instanceof OpenGeniApiError && error.status >= 400 && error.status < 500)
|
|
3072
|
+
) {
|
|
3073
|
+
throw error;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (!page) throw new OpenGeniApiError(502, "retained screenshot range retry exhausted");
|
|
3078
|
+
const expectedLength = end - start + 1;
|
|
3079
|
+
if (
|
|
3080
|
+
page.status !== 206 ||
|
|
3081
|
+
page.contentType !== metadata.contentType ||
|
|
3082
|
+
page.contentLength !== expectedLength ||
|
|
3083
|
+
page.contentRange !== `bytes ${start}-${end}/${metadata.originalBytes}`
|
|
3084
|
+
) {
|
|
3085
|
+
throw new OpenGeniApiError(502, "retained screenshot range response is invalid");
|
|
3086
|
+
}
|
|
3087
|
+
bytes.set(page.bytes, start);
|
|
3088
|
+
}
|
|
3089
|
+
if ((await sha256Hex(bytes)) !== metadata.sha256) {
|
|
3090
|
+
throw new OpenGeniApiError(502, "retained screenshot checksum mismatch");
|
|
3091
|
+
}
|
|
3092
|
+
return { metadata, bytes };
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
private async getRetainedArtifactContentAtPath(
|
|
3096
|
+
path: string,
|
|
3097
|
+
options: RetainedArtifactContentOptions,
|
|
2822
3098
|
): Promise<RetainedArtifactContent> {
|
|
2823
3099
|
if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
|
|
2824
3100
|
throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
|
|
2825
3101
|
}
|
|
2826
3102
|
const correlationId = crypto.randomUUID();
|
|
2827
|
-
const response = await this.fetchImpl(
|
|
2828
|
-
|
|
2829
|
-
{
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
Accept: "application/octet-stream",
|
|
2834
|
-
...(options.range ? { Range: options.range } : {}),
|
|
2835
|
-
},
|
|
2836
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
3103
|
+
const response = await this.fetchImpl(this.url(path), {
|
|
3104
|
+
method: "GET",
|
|
3105
|
+
headers: {
|
|
3106
|
+
...this.headers(correlationId),
|
|
3107
|
+
Accept: "application/octet-stream",
|
|
3108
|
+
...(options.range ? { Range: options.range } : {}),
|
|
2837
3109
|
},
|
|
2838
|
-
|
|
3110
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
3111
|
+
});
|
|
2839
3112
|
try {
|
|
2840
3113
|
assertApiContractResponse(response);
|
|
2841
3114
|
} catch (error) {
|
|
@@ -3772,7 +4045,12 @@ function isUploadTranscriptionRecordingChunkResponse(
|
|
|
3772
4045
|
): value is UploadTranscriptionRecordingChunkResponse {
|
|
3773
4046
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3774
4047
|
const record = value as Record<string, unknown>;
|
|
3775
|
-
if (
|
|
4048
|
+
if (
|
|
4049
|
+
!isTranscriptionRecordingResponse({
|
|
4050
|
+
recording: record.recording,
|
|
4051
|
+
segments: [],
|
|
4052
|
+
})
|
|
4053
|
+
) {
|
|
3776
4054
|
return false;
|
|
3777
4055
|
}
|
|
3778
4056
|
if (!record.chunk || typeof record.chunk !== "object" || Array.isArray(record.chunk)) {
|
|
@@ -3947,6 +4225,12 @@ async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<strin
|
|
|
3947
4225
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3948
4226
|
}
|
|
3949
4227
|
|
|
4228
|
+
async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
|
4229
|
+
const owned = Uint8Array.from(bytes);
|
|
4230
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", owned.buffer);
|
|
4231
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4232
|
+
}
|
|
4233
|
+
|
|
3950
4234
|
async function cancelResponseBody(response: Response, reason: string): Promise<void> {
|
|
3951
4235
|
await response.body?.cancel(reason).catch(() => undefined);
|
|
3952
4236
|
}
|
package/src/index.ts
CHANGED
|
@@ -578,6 +578,8 @@ export type {
|
|
|
578
578
|
FsEncoding,
|
|
579
579
|
FsListRequest,
|
|
580
580
|
FsListResponse,
|
|
581
|
+
FsListBatchRequest,
|
|
582
|
+
FsListBatchResponse,
|
|
581
583
|
FsReadRequest,
|
|
582
584
|
FsReadResponse,
|
|
583
585
|
FsWriteRequest,
|
|
@@ -598,6 +600,10 @@ export type {
|
|
|
598
600
|
GitFileDiff,
|
|
599
601
|
GitDiffRequest,
|
|
600
602
|
GitDiffResponse,
|
|
603
|
+
GitReadBatchItemRequest,
|
|
604
|
+
GitReadBatchRequest,
|
|
605
|
+
GitReadBatchItemResponse,
|
|
606
|
+
GitReadBatchResponse,
|
|
601
607
|
GitLogRequest,
|
|
602
608
|
GitCommit,
|
|
603
609
|
GitLogResponse,
|
|
@@ -646,6 +652,7 @@ export type {
|
|
|
646
652
|
Workspace,
|
|
647
653
|
WorkspaceControlEvent,
|
|
648
654
|
VariableSet,
|
|
655
|
+
VariableSetSecret,
|
|
649
656
|
VariableSetVariableMetadata,
|
|
650
657
|
Rig,
|
|
651
658
|
RigVersion,
|
|
@@ -677,6 +684,8 @@ export type {
|
|
|
677
684
|
MachineView,
|
|
678
685
|
MachinesResponse,
|
|
679
686
|
MachineMetricsSeriesResponse,
|
|
687
|
+
RemoveEnrollmentRequest,
|
|
688
|
+
RemoveEnrollmentResponse,
|
|
680
689
|
// Bring-your-own-compute: the user-authenticated active-sandbox swap (M7).
|
|
681
690
|
SwapActiveSandboxRequest,
|
|
682
691
|
SwapActiveSandboxResponse,
|