@opengeni/sdk 0.44.6 → 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.
@@ -82,6 +82,9 @@ async function* streamSessionEvents(transport, options = {}) {
82
82
  const reconnect = options.reconnect ?? true;
83
83
  const baseDelayMs = options.reconnectDelayMs ?? 500;
84
84
  const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
85
+ const jitterRatio = options.reconnectJitterRatio ?? 0.2;
86
+ const beforeLiveTimeoutMs = options.beforeLiveTimeoutMs ?? 15e3;
87
+ const heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? 45e3;
85
88
  const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
86
89
  let cursor = options.after ?? 0;
87
90
  let failedAttempts = 0;
@@ -96,9 +99,15 @@ async function* streamSessionEvents(transport, options = {}) {
96
99
  everConnected = true;
97
100
  failedAttempts = 0;
98
101
  delayMs = baseDelayMs;
99
- await options.beforeLive?.();
102
+ try {
103
+ options.onOpen?.();
104
+ } catch {
105
+ }
106
+ await runBeforeLive(options.beforeLive, beforeLiveTimeoutMs, signal);
100
107
  options.onStateChange?.("live");
101
- for await (const message of parseSseStream(body)) {
108
+ for await (const message of parseSseStream(
109
+ withStreamInactivityTimeout(body, heartbeatTimeoutMs, signal)
110
+ )) {
102
111
  if (signal?.aborted) {
103
112
  return;
104
113
  }
@@ -122,7 +131,7 @@ async function* streamSessionEvents(transport, options = {}) {
122
131
  return;
123
132
  }
124
133
  if (cursor === cursorAtOpen) {
125
- await sleep(baseDelayMs, signal);
134
+ await sleep(jitteredDelay(baseDelayMs, jitterRatio), signal);
126
135
  }
127
136
  continue;
128
137
  } catch (error) {
@@ -139,10 +148,76 @@ async function* streamSessionEvents(transport, options = {}) {
139
148
  );
140
149
  }
141
150
  }
142
- await sleep(delayMs, signal);
151
+ await sleep(jitteredDelay(delayMs, jitterRatio), signal);
143
152
  delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
144
153
  }
145
154
  }
155
+ async function runBeforeLive(beforeLive, timeoutMs, signal) {
156
+ if (!beforeLive) return;
157
+ let timer;
158
+ let abortListener;
159
+ const timeout = new Promise((_resolve, reject) => {
160
+ timer = setTimeout(
161
+ () => reject(new TypeError(`stream reconciliation timed out after ${timeoutMs}ms`)),
162
+ timeoutMs
163
+ );
164
+ if (signal) {
165
+ abortListener = () => reject(new DOMException("Aborted", "AbortError"));
166
+ signal.addEventListener("abort", abortListener, { once: true });
167
+ }
168
+ });
169
+ try {
170
+ await Promise.race([Promise.resolve().then(beforeLive), timeout]);
171
+ } finally {
172
+ if (timer !== void 0) clearTimeout(timer);
173
+ if (signal && abortListener) signal.removeEventListener("abort", abortListener);
174
+ }
175
+ }
176
+ function jitteredDelay(delayMs, ratio) {
177
+ if (delayMs <= 0 || ratio <= 0) return delayMs;
178
+ const boundedRatio = Math.min(ratio, 1);
179
+ const spread = delayMs * boundedRatio;
180
+ return Math.max(0, delayMs - spread + Math.random() * spread * 2);
181
+ }
182
+ function withStreamInactivityTimeout(stream, timeoutMs, signal) {
183
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
184
+ throw new RangeError("stream heartbeat timeout must be a positive safe integer");
185
+ }
186
+ const reader = stream.getReader();
187
+ return new ReadableStream({
188
+ pull: async (controller) => {
189
+ let timer;
190
+ let abortListener;
191
+ try {
192
+ const result = await Promise.race([
193
+ reader.read(),
194
+ new Promise((_resolve, reject) => {
195
+ timer = setTimeout(
196
+ () => reject(new TypeError(`event stream heartbeat timed out after ${timeoutMs}ms`)),
197
+ timeoutMs
198
+ );
199
+ if (signal) {
200
+ abortListener = () => reject(new DOMException("Aborted", "AbortError"));
201
+ signal.addEventListener("abort", abortListener, { once: true });
202
+ }
203
+ })
204
+ ]);
205
+ if (result.done) controller.close();
206
+ else controller.enqueue(result.value);
207
+ } catch (error) {
208
+ void reader.cancel(error).catch(() => {
209
+ });
210
+ controller.error(error);
211
+ } finally {
212
+ if (timer !== void 0) clearTimeout(timer);
213
+ if (signal && abortListener) signal.removeEventListener("abort", abortListener);
214
+ }
215
+ },
216
+ cancel: async (reason) => {
217
+ await reader.cancel(reason);
218
+ }
219
+ });
220
+ }
146
221
  async function* backfillEvents(transport, fromExclusive, toInclusive) {
147
222
  let cursor = fromExclusive;
148
223
  while (cursor < toInclusive) {
@@ -197,6 +272,9 @@ async function* streamWorkspaceControlEvents(transport, options = {}) {
197
272
  const reconnect = options.reconnect ?? true;
198
273
  const baseDelayMs = options.reconnectDelayMs ?? 500;
199
274
  const maxDelayMs = options.maxReconnectDelayMs ?? 1e4;
275
+ const jitterRatio = options.reconnectJitterRatio ?? 0.2;
276
+ const beforeLiveTimeoutMs = options.beforeLiveTimeoutMs ?? 15e3;
277
+ const heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? 45e3;
200
278
  const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
201
279
  let cursor = options.after ?? 0;
202
280
  let failures = 0;
@@ -211,9 +289,11 @@ async function* streamWorkspaceControlEvents(transport, options = {}) {
211
289
  everConnected = true;
212
290
  failures = 0;
213
291
  delayMs = baseDelayMs;
214
- await options.beforeLive?.();
292
+ await runBeforeLive(options.beforeLive, beforeLiveTimeoutMs, signal);
215
293
  options.onStateChange?.("live");
216
- for await (const message of parseSseStream(body)) {
294
+ for await (const message of parseSseStream(
295
+ withStreamInactivityTimeout(body, heartbeatTimeoutMs, signal)
296
+ )) {
217
297
  if (signal?.aborted) return;
218
298
  const event = parseWorkspaceControlEvent(message.data);
219
299
  if (!event || event.sequence <= cursor) continue;
@@ -221,7 +301,7 @@ async function* streamWorkspaceControlEvents(transport, options = {}) {
221
301
  yield event;
222
302
  }
223
303
  if (!reconnect) return;
224
- if (cursor === cursorAtOpen) await sleep2(baseDelayMs, signal);
304
+ if (cursor === cursorAtOpen) await sleep2(jitteredDelay(baseDelayMs, jitterRatio), signal);
225
305
  continue;
226
306
  } catch (error) {
227
307
  if (signal?.aborted || isAbortError(error)) return;
@@ -233,7 +313,7 @@ async function* streamWorkspaceControlEvents(transport, options = {}) {
233
313
  );
234
314
  }
235
315
  }
236
- await sleep2(delayMs, signal);
316
+ await sleep2(jitteredDelay(delayMs, jitterRatio), signal);
237
317
  delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
238
318
  }
239
319
  }
@@ -421,8 +501,14 @@ var KNOWN_PERMISSIONS = [
421
501
  "connections:write",
422
502
  "environments:manage",
423
503
  "environments:use",
504
+ "variable-sets:list",
505
+ "variable-sets:read",
506
+ "variable-sets:write",
424
507
  "variable-sets:manage",
425
508
  "variable-sets:use",
509
+ "secrets:list",
510
+ "secrets:read",
511
+ "secrets:write",
426
512
  "mcp_servers:attach",
427
513
  "toolspace:call",
428
514
  "goals:manage",
@@ -438,6 +524,7 @@ var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
438
524
  var OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id";
439
525
  var RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
440
526
  var RETAINED_OUTPUT_MAX_PAGE_BYTES = 1024 * 1024;
527
+ var COMPUTER_SCREENSHOT_MAX_BYTES = 32 * 1024 * 1024;
441
528
  var KNOWN_USAGE_EVENT_TYPES = [
442
529
  "agent_run.created",
443
530
  "agent_run.completed",
@@ -465,6 +552,8 @@ var OpenGeniClient = class {
465
552
  baseUrl;
466
553
  options;
467
554
  fetchImpl;
555
+ readInFlight = /* @__PURE__ */ new Map();
556
+ readTrailing = /* @__PURE__ */ new Map();
468
557
  constructor(options) {
469
558
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
470
559
  this.options = options;
@@ -476,7 +565,11 @@ var OpenGeniClient = class {
476
565
  const correlationId = crypto.randomUUID();
477
566
  const form = new FormData();
478
567
  const filename = filenameForAudioMimeType(input.mimeType);
479
- const audio = input.audio instanceof File ? input.audio : input.audio instanceof Uint8Array ? new File([Uint8Array.from(input.audio)], filename, { type: input.mimeType }) : new File([input.audio], filename, { type: input.mimeType || input.audio.type });
568
+ const audio = input.audio instanceof File ? input.audio : input.audio instanceof Uint8Array ? new File([Uint8Array.from(input.audio)], filename, {
569
+ type: input.mimeType
570
+ }) : new File([input.audio], filename, {
571
+ type: input.mimeType || input.audio.type
572
+ });
480
573
  form.append("audio", audio, filename);
481
574
  form.append("mimeType", input.mimeType);
482
575
  if (input.durationSeconds !== void 0) {
@@ -486,7 +579,10 @@ var OpenGeniClient = class {
486
579
  try {
487
580
  response = await this.fetchImpl(this.url(`/v1/workspaces/${workspaceId}/transcriptions`), {
488
581
  method: "POST",
489
- headers: { ...this.headers(correlationId), Accept: "application/json" },
582
+ headers: {
583
+ ...this.headers(correlationId),
584
+ Accept: "application/json"
585
+ },
490
586
  body: form,
491
587
  ...input.signal ? { signal: input.signal } : {}
492
588
  });
@@ -495,7 +591,11 @@ var OpenGeniClient = class {
495
591
  throw mutationTransportError(correlationId);
496
592
  }
497
593
  assertApiContractResponse(response);
498
- if (!response.ok) throw await apiErrorFromResponse(response, { method: "POST", correlationId });
594
+ if (!response.ok)
595
+ throw await apiErrorFromResponse(response, {
596
+ method: "POST",
597
+ correlationId
598
+ });
499
599
  await assertJsonResponse(response, { method: "POST", correlationId });
500
600
  let body;
501
601
  try {
@@ -516,6 +616,123 @@ var OpenGeniClient = class {
516
616
  }
517
617
  return body;
518
618
  }
619
+ async createTranscriptionRecording(workspaceId, input) {
620
+ return transcriptionRecordingResponse(
621
+ await this.requestJson(
622
+ "POST",
623
+ `/v1/workspaces/${workspaceId}/transcription-recordings`,
624
+ { recordingId: input.recordingId, mimeType: input.mimeType },
625
+ {},
626
+ input.signal ? { signal: input.signal } : {}
627
+ )
628
+ );
629
+ }
630
+ async getTranscriptionRecording(workspaceId, recordingId, options = {}) {
631
+ return transcriptionRecordingResponse(
632
+ await this.requestJson(
633
+ "GET",
634
+ `/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}`,
635
+ void 0,
636
+ {},
637
+ options.signal ? { signal: options.signal } : {}
638
+ )
639
+ );
640
+ }
641
+ async listTranscriptionRecordings(workspaceId, options = {}) {
642
+ const body = await this.requestJson(
643
+ "GET",
644
+ `/v1/workspaces/${workspaceId}/transcription-recordings`,
645
+ void 0,
646
+ {},
647
+ options.signal ? { signal: options.signal } : {}
648
+ );
649
+ if (!isTranscriptionRecordingListResponse(body)) {
650
+ throw new OpenGeniApiError(502, "Invalid transcription recording list response.", {
651
+ code: "invalid_response"
652
+ });
653
+ }
654
+ return body;
655
+ }
656
+ async uploadTranscriptionRecordingChunk(workspaceId, recordingId, chunkNumber, input) {
657
+ const correlationId = crypto.randomUUID();
658
+ let response;
659
+ try {
660
+ response = await this.fetchImpl(
661
+ this.url(
662
+ `/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/chunks/${chunkNumber}`
663
+ ),
664
+ {
665
+ method: "PUT",
666
+ headers: {
667
+ ...this.headers(correlationId),
668
+ Accept: "application/json",
669
+ "Content-Type": input.mimeType,
670
+ "x-opengeni-chunk-sha256": input.sha256,
671
+ "x-opengeni-chunk-start-milliseconds": String(input.startMilliseconds),
672
+ "x-opengeni-chunk-duration-milliseconds": String(input.durationMilliseconds)
673
+ },
674
+ body: input.audio instanceof Uint8Array ? Uint8Array.from(input.audio) : input.audio,
675
+ ...input.signal ? { signal: input.signal } : {}
676
+ }
677
+ );
678
+ } catch (error) {
679
+ if (input.signal?.aborted) throw error;
680
+ throw mutationTransportError(correlationId);
681
+ }
682
+ assertApiContractResponse(response);
683
+ if (!response.ok)
684
+ throw await apiErrorFromResponse(response, {
685
+ method: "PUT",
686
+ correlationId
687
+ });
688
+ await assertJsonResponse(response, { method: "PUT", correlationId });
689
+ const body = await response.json().catch(() => null);
690
+ if (!isUploadTranscriptionRecordingChunkResponse(body)) {
691
+ throw new OpenGeniApiError(response.status, "Invalid transcription chunk response.", {
692
+ code: "invalid_response",
693
+ mutation: true,
694
+ correlationId
695
+ });
696
+ }
697
+ return body;
698
+ }
699
+ async finalizeTranscriptionRecording(workspaceId, recordingId, input) {
700
+ return transcriptionRecordingResponse(
701
+ await this.requestJson(
702
+ "POST",
703
+ `/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/finalize`,
704
+ {
705
+ chunkCount: input.chunkCount,
706
+ totalBytes: input.totalBytes,
707
+ totalDurationMilliseconds: input.totalDurationMilliseconds
708
+ },
709
+ {},
710
+ input.signal ? { signal: input.signal } : {}
711
+ )
712
+ );
713
+ }
714
+ async processNextTranscriptionRecordingSegment(workspaceId, recordingId, options = {}) {
715
+ return transcriptionRecordingResponse(
716
+ await this.requestJson(
717
+ "POST",
718
+ `/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/process-next`,
719
+ {},
720
+ {},
721
+ options.signal ? { signal: options.signal } : {}
722
+ )
723
+ );
724
+ }
725
+ async discardTranscriptionRecording(workspaceId, recordingId, options = {}) {
726
+ return transcriptionRecordingResponse(
727
+ await this.requestJson(
728
+ "DELETE",
729
+ `/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}`,
730
+ void 0,
731
+ {},
732
+ options.signal ? { signal: options.signal } : {}
733
+ )
734
+ );
735
+ }
519
736
  async createSession(workspaceId, request) {
520
737
  return await this.requestJson(
521
738
  "POST",
@@ -536,11 +753,9 @@ var OpenGeniClient = class {
536
753
  request
537
754
  );
538
755
  }
539
- async getSession(workspaceId, sessionId) {
540
- return await this.requestJson(
541
- "GET",
542
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}`
543
- );
756
+ async getSession(workspaceId, sessionId, options = {}) {
757
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}`;
758
+ return await this.singleFlightRead(path, () => this.requestJson("GET", path), options);
544
759
  }
545
760
  async updateSession(workspaceId, sessionId, request) {
546
761
  return await this.requestJson(
@@ -633,10 +848,34 @@ var OpenGeniClient = class {
633
848
  );
634
849
  }
635
850
  async getSessionLineage(workspaceId, sessionId) {
636
- return await this.requestJson(
637
- "GET",
638
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`
639
- );
851
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
852
+ return await this.singleFlightRead(
853
+ path,
854
+ () => this.requestJson("GET", path)
855
+ );
856
+ }
857
+ singleFlightRead(key, read, options = {}) {
858
+ const existing = this.readInFlight.get(key);
859
+ if (existing) {
860
+ if (!options.fresh) return existing;
861
+ const queued = this.readTrailing.get(key);
862
+ if (queued) return queued;
863
+ const trailing = existing.then(
864
+ () => this.singleFlightRead(key, read),
865
+ () => this.singleFlightRead(key, read)
866
+ );
867
+ this.readTrailing.set(key, trailing);
868
+ const clear = () => {
869
+ if (this.readTrailing.get(key) === trailing) this.readTrailing.delete(key);
870
+ };
871
+ void trailing.then(clear, clear);
872
+ return trailing;
873
+ }
874
+ const promise = read().finally(() => {
875
+ if (this.readInFlight.get(key) === promise) this.readInFlight.delete(key);
876
+ });
877
+ this.readInFlight.set(key, promise);
878
+ return promise;
640
879
  }
641
880
  /** Negotiate one server-mediated connected-Codex GPT-Live V3 WebRTC call. */
642
881
  async negotiateCodexRealtimeWebrtc(workspaceId, sessionId, request, options = {}) {
@@ -713,7 +952,9 @@ var OpenGeniClient = class {
713
952
  }
714
953
  /** Newest turn that durably emitted `turn.started`, or null before any admission. */
715
954
  async getLatestStartedTurn(workspaceId, sessionId) {
716
- const turns = await this.listTurns(workspaceId, sessionId, { latestStarted: true });
955
+ const turns = await this.listTurns(workspaceId, sessionId, {
956
+ latestStarted: true
957
+ });
717
958
  return turns[0] ?? null;
718
959
  }
719
960
  // --- Bring-your-own-compute: Machines dashboard + metrics (M10) ------------
@@ -747,6 +988,19 @@ var OpenGeniClient = class {
747
988
  );
748
989
  return response.samples;
749
990
  }
991
+ /**
992
+ * Remove one connected self-hosted machine enrollment. The control-plane
993
+ * operation works while the agent is offline, revokes future reconnects,
994
+ * retains history, and returns a typed blocker when active route/lease or
995
+ * recovery dependencies make removal unsafe. `idempotencyKey` is replay-safe.
996
+ */
997
+ async removeEnrollment(workspaceId, enrollmentId, request = {}) {
998
+ return await this.requestJson(
999
+ "POST",
1000
+ `/v1/workspaces/${workspaceId}/enrollments/${enrollmentId}/revoke`,
1001
+ request
1002
+ );
1003
+ }
750
1004
  // --- Self-hosted enrollment UX (design 11) --------------------------------
751
1005
  /**
752
1006
  * Resolve a pending device-enrollment flow by its user_code for the click-Grant
@@ -1031,9 +1285,10 @@ var OpenGeniClient = class {
1031
1285
  }
1032
1286
  // --- Turn queue ------------------------------------------------------------
1033
1287
  async getQueue(workspaceId, sessionId) {
1034
- return await this.requestJson(
1035
- "GET",
1036
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`
1288
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`;
1289
+ return await this.singleFlightRead(
1290
+ path,
1291
+ () => this.requestJson("GET", path)
1037
1292
  );
1038
1293
  }
1039
1294
  async moveQueueItem(workspaceId, sessionId, turnId, request) {
@@ -1201,10 +1456,8 @@ var OpenGeniClient = class {
1201
1456
  // --- Goals -------------------------------------------------------------------
1202
1457
  /** The session's goal. 404s when the session never had one. */
1203
1458
  async getGoal(workspaceId, sessionId) {
1204
- return await this.requestJson(
1205
- "GET",
1206
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`
1207
- );
1459
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
1460
+ return await this.singleFlightRead(path, () => this.requestJson("GET", path));
1208
1461
  }
1209
1462
  async updateGoal(workspaceId, sessionId, request) {
1210
1463
  return await this.requestJson(
@@ -1264,6 +1517,16 @@ var OpenGeniClient = class {
1264
1517
  options
1265
1518
  );
1266
1519
  }
1520
+ /** FileSystem: hydrate several independent directories behind one sandbox lease. */
1521
+ async fsListBatch(workspaceId, sessionId, request, options = {}) {
1522
+ return await this.requestJson(
1523
+ "POST",
1524
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list-batch`,
1525
+ request,
1526
+ {},
1527
+ options
1528
+ );
1529
+ }
1267
1530
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
1268
1531
  async fsRead(workspaceId, sessionId, request, options = {}) {
1269
1532
  return await this.requestJson(
@@ -1326,6 +1589,16 @@ var OpenGeniClient = class {
1326
1589
  options
1327
1590
  );
1328
1591
  }
1592
+ /** Git: read status and optional diffs for several repositories behind one sandbox lease. */
1593
+ async gitReadBatch(workspaceId, sessionId, request, options = {}) {
1594
+ return await this.requestJson(
1595
+ "POST",
1596
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/read-batch`,
1597
+ request,
1598
+ {},
1599
+ options
1600
+ );
1601
+ }
1329
1602
  /** Git: commit log. */
1330
1603
  async gitLog(workspaceId, sessionId, request = {}) {
1331
1604
  return await this.requestJson(
@@ -1798,7 +2071,8 @@ var OpenGeniClient = class {
1798
2071
  );
1799
2072
  }
1800
2073
  // --- VariableSets --------------------------------------------------------------
1801
- // Variable values are write-only: reads return name/version metadata only.
2074
+ // Generic reads return name/version metadata only. Plaintext uses one
2075
+ // dedicated permissioned endpoint.
1802
2076
  async listVariableSets(workspaceId) {
1803
2077
  return await this.requestJson(
1804
2078
  "GET",
@@ -1818,6 +2092,12 @@ var OpenGeniClient = class {
1818
2092
  `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
1819
2093
  );
1820
2094
  }
2095
+ async getVariableSetVariable(workspaceId, variableSetId, name) {
2096
+ return await this.requestJson(
2097
+ "GET",
2098
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`
2099
+ );
2100
+ }
1821
2101
  async updateVariableSet(workspaceId, variableSetId, request) {
1822
2102
  return await this.requestJson(
1823
2103
  "PATCH",
@@ -1831,7 +2111,7 @@ var OpenGeniClient = class {
1831
2111
  `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}`
1832
2112
  );
1833
2113
  }
1834
- /** Create or rotate a variable. The value never comes back on any read. */
2114
+ /** Create or rotate a variable. Generic reads never return its value. */
1835
2115
  async setVariableSetVariable(workspaceId, variableSetId, name, value) {
1836
2116
  return await this.requestJson(
1837
2117
  "PUT",
@@ -1963,18 +2243,23 @@ var OpenGeniClient = class {
1963
2243
  }
1964
2244
  // --- Files -----------------------------------------------------------------------
1965
2245
  /** Step 1 of the upload flow: returns the pre-signed PUT target. */
1966
- async beginFileUpload(workspaceId, request) {
2246
+ async beginFileUpload(workspaceId, request, options = {}) {
1967
2247
  return await this.requestJson(
1968
2248
  "POST",
1969
2249
  `/v1/workspaces/${workspaceId}/files/uploads`,
1970
- request
2250
+ request,
2251
+ {},
2252
+ options
1971
2253
  );
1972
2254
  }
1973
2255
  /** Step 3 of the upload flow: server verifies the object and marks it ready. */
1974
- async completeFileUpload(workspaceId, uploadId) {
2256
+ async completeFileUpload(workspaceId, uploadId, options = {}) {
1975
2257
  const response = await this.requestJson(
1976
2258
  "POST",
1977
- `/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`
2259
+ `/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`,
2260
+ void 0,
2261
+ {},
2262
+ options
1978
2263
  );
1979
2264
  return response.file;
1980
2265
  }
@@ -1984,35 +2269,68 @@ var OpenGeniClient = class {
1984
2269
  * -> complete. Returns the ready `FileAsset`.
1985
2270
  */
1986
2271
  async uploadFile(workspaceId, input) {
2272
+ if (input.timeoutMs !== void 0 && (!Number.isFinite(input.timeoutMs) || input.timeoutMs <= 0)) {
2273
+ throw new Error("File upload timeout must be a positive number");
2274
+ }
2275
+ const withTimeout = async (timeoutMs, operation) => {
2276
+ const controller = new AbortController();
2277
+ let timeout;
2278
+ const timedOut = new Promise((_resolve, reject) => {
2279
+ timeout = setTimeout(() => {
2280
+ controller.abort();
2281
+ reject(new Error("File upload timed out. Retry the upload."));
2282
+ }, timeoutMs);
2283
+ });
2284
+ try {
2285
+ return await Promise.race([operation(controller.signal), timedOut]);
2286
+ } finally {
2287
+ if (timeout !== void 0) clearTimeout(timeout);
2288
+ }
2289
+ };
1987
2290
  const body = input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data instanceof ArrayBuffer ? input.data.slice(0) : input.data;
1988
2291
  const sizeBytes = typeof body === "string" ? new TextEncoder().encode(body).byteLength : body instanceof Blob ? body.size : body.byteLength;
1989
2292
  const sha256 = input.sha256 ?? await sha256ForUpload(body);
1990
- const upload = await this.beginFileUpload(workspaceId, {
1991
- filename: input.filename,
1992
- contentType: input.contentType,
1993
- sizeBytes,
1994
- sha256
1995
- });
1996
- const putResponse = await this.fetchImpl(upload.putUrl, {
1997
- method: "PUT",
1998
- // Signed object-storage URLs carry their own short-lived authority.
1999
- // Browser cookies and HTTP auth must never accompany this cross-origin
2000
- // request: credentialed fetches are incompatible with wildcard CORS and
2001
- // can leak ambient credentials to a caller-selected storage endpoint.
2002
- credentials: "omit",
2003
- // The backend's requiredHeaders already carry the canonical lowercase
2004
- // `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
2005
- // a `Content-Type` key here: WHATWG Headers treats the two casings as the
2006
- // same header and comma-joins their values (e.g. "text/plain, text/plain"),
2007
- // which the object store persists verbatim and COMPLETE then rejects (422),
2008
- // and which breaks S3's presigned-URL signature.
2009
- headers: { ...upload.requiredHeaders },
2010
- body
2011
- });
2293
+ const upload = await withTimeout(
2294
+ 3e4,
2295
+ async (signal) => await this.beginFileUpload(
2296
+ workspaceId,
2297
+ {
2298
+ filename: input.filename,
2299
+ contentType: input.contentType,
2300
+ sizeBytes,
2301
+ sha256
2302
+ },
2303
+ { signal }
2304
+ )
2305
+ );
2306
+ const transferTimeoutMs = input.timeoutMs ?? Math.max(12e4, Math.ceil(sizeBytes / (256 * 1024)) * 1e3);
2307
+ const putResponse = await withTimeout(
2308
+ transferTimeoutMs,
2309
+ async (signal) => await this.fetchImpl(upload.putUrl, {
2310
+ method: "PUT",
2311
+ // Signed object-storage URLs carry their own short-lived authority.
2312
+ // Browser cookies and HTTP auth must never accompany this cross-origin
2313
+ // request: credentialed fetches are incompatible with wildcard CORS and
2314
+ // can leak ambient credentials to a caller-selected storage endpoint.
2315
+ credentials: "omit",
2316
+ // The backend's requiredHeaders already carry the canonical lowercase
2317
+ // `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
2318
+ // a `Content-Type` key here: WHATWG Headers treats the two casings as the
2319
+ // same header and comma-joins their values (e.g. "text/plain, text/plain"),
2320
+ // which the object store persists verbatim and COMPLETE then rejects (422),
2321
+ // and which breaks S3's presigned-URL signature.
2322
+ headers: { ...upload.requiredHeaders },
2323
+ body,
2324
+ signal
2325
+ })
2326
+ );
2012
2327
  if (!putResponse.ok) {
2013
2328
  throw await apiErrorFromResponse(putResponse, { method: "PUT" });
2014
2329
  }
2015
- return await this.completeFileUpload(workspaceId, upload.uploadId);
2330
+ return await withTimeout(
2331
+ 3e4,
2332
+ async (signal) => await this.completeFileUpload(workspaceId, upload.uploadId, { signal })
2333
+ );
2016
2334
  }
2017
2335
  async getFile(workspaceId, fileId) {
2018
2336
  return await this.requestJson(
@@ -2032,22 +2350,83 @@ var OpenGeniClient = class {
2032
2350
  * deliberately does not use the ordinary signed file-download URL.
2033
2351
  */
2034
2352
  async getRetainedArtifactContent(workspaceId, artifactId, options = {}) {
2353
+ return await this.getRetainedArtifactContentAtPath(
2354
+ `/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`,
2355
+ options
2356
+ );
2357
+ }
2358
+ async getSessionRetainedArtifact(workspaceId, sessionId, artifactId) {
2359
+ return await this.requestJson(
2360
+ "GET",
2361
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}`
2362
+ );
2363
+ }
2364
+ async getSessionRetainedArtifactContent(workspaceId, sessionId, artifactId, options = {}) {
2365
+ return await this.getRetainedArtifactContentAtPath(
2366
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}/content`,
2367
+ options
2368
+ );
2369
+ }
2370
+ /** Assemble one retained screenshot from bounded authenticated API ranges. */
2371
+ async downloadRetainedScreenshot(workspaceId, sessionId, artifactId, options = {}) {
2372
+ const maxRetries = options.maxRetries ?? 2;
2373
+ if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 3) {
2374
+ throw new RangeError("retained screenshot maxRetries must be an integer from 0 to 3");
2375
+ }
2376
+ const metadata = await this.getSessionRetainedArtifact(workspaceId, sessionId, artifactId);
2377
+ if (!metadata.available) return { metadata, bytes: null };
2378
+ if (metadata.kind !== "computer_screenshot" || metadata.contentType !== "image/png" || !metadata.dimensions || metadata.originalBytes <= 0 || metadata.originalBytes > COMPUTER_SCREENSHOT_MAX_BYTES) {
2379
+ throw new OpenGeniApiError(502, "retained screenshot metadata is invalid");
2380
+ }
2381
+ const bytes = new Uint8Array(metadata.originalBytes);
2382
+ const pageBytes = Math.min(metadata.retrieval.maxRangeBytes, RETAINED_OUTPUT_MAX_PAGE_BYTES);
2383
+ if (!Number.isSafeInteger(pageBytes) || pageBytes <= 0) {
2384
+ throw new OpenGeniApiError(502, "retained screenshot range metadata is invalid");
2385
+ }
2386
+ for (let start = 0; start < bytes.byteLength; start += pageBytes) {
2387
+ options.signal?.throwIfAborted();
2388
+ const end = Math.min(start + pageBytes, bytes.byteLength) - 1;
2389
+ let page = null;
2390
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
2391
+ try {
2392
+ page = await this.getSessionRetainedArtifactContent(workspaceId, sessionId, artifactId, {
2393
+ range: `bytes=${start}-${end}`,
2394
+ ...options.signal ? { signal: options.signal } : {}
2395
+ });
2396
+ break;
2397
+ } catch (error) {
2398
+ options.signal?.throwIfAborted();
2399
+ if (attempt >= maxRetries || error instanceof OpenGeniApiError && error.status >= 400 && error.status < 500) {
2400
+ throw error;
2401
+ }
2402
+ }
2403
+ }
2404
+ if (!page) throw new OpenGeniApiError(502, "retained screenshot range retry exhausted");
2405
+ const expectedLength = end - start + 1;
2406
+ if (page.status !== 206 || page.contentType !== metadata.contentType || page.contentLength !== expectedLength || page.contentRange !== `bytes ${start}-${end}/${metadata.originalBytes}`) {
2407
+ throw new OpenGeniApiError(502, "retained screenshot range response is invalid");
2408
+ }
2409
+ bytes.set(page.bytes, start);
2410
+ }
2411
+ if (await sha256Hex(bytes) !== metadata.sha256) {
2412
+ throw new OpenGeniApiError(502, "retained screenshot checksum mismatch");
2413
+ }
2414
+ return { metadata, bytes };
2415
+ }
2416
+ async getRetainedArtifactContentAtPath(path, options) {
2035
2417
  if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
2036
2418
  throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
2037
2419
  }
2038
2420
  const correlationId = crypto.randomUUID();
2039
- const response = await this.fetchImpl(
2040
- this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
2041
- {
2042
- method: "GET",
2043
- headers: {
2044
- ...this.headers(correlationId),
2045
- Accept: "application/octet-stream",
2046
- ...options.range ? { Range: options.range } : {}
2047
- },
2048
- ...options.signal ? { signal: options.signal } : {}
2049
- }
2050
- );
2421
+ const response = await this.fetchImpl(this.url(path), {
2422
+ method: "GET",
2423
+ headers: {
2424
+ ...this.headers(correlationId),
2425
+ Accept: "application/octet-stream",
2426
+ ...options.range ? { Range: options.range } : {}
2427
+ },
2428
+ ...options.signal ? { signal: options.signal } : {}
2429
+ });
2051
2430
  try {
2052
2431
  assertApiContractResponse(response);
2053
2432
  } catch (error) {
@@ -2727,6 +3106,41 @@ function isTranscribeAudioResponse(value) {
2727
3106
  const record = value;
2728
3107
  return typeof record.text === "string" && Array.isArray(record.languages) && record.languages.every((language) => typeof language === "string");
2729
3108
  }
3109
+ function isUploadTranscriptionRecordingChunkResponse(value) {
3110
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3111
+ const record = value;
3112
+ if (!isTranscriptionRecordingResponse({
3113
+ recording: record.recording,
3114
+ segments: []
3115
+ })) {
3116
+ return false;
3117
+ }
3118
+ if (!record.chunk || typeof record.chunk !== "object" || Array.isArray(record.chunk)) {
3119
+ return false;
3120
+ }
3121
+ const chunk = record.chunk;
3122
+ return typeof chunk.chunkNumber === "number" && typeof chunk.byteLength === "number" && typeof chunk.sha256 === "string" && typeof chunk.startMilliseconds === "number" && typeof chunk.durationMilliseconds === "number" && typeof chunk.deduplicated === "boolean";
3123
+ }
3124
+ function isTranscriptionRecordingResponse(value) {
3125
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3126
+ const response = value;
3127
+ if (!response.recording || typeof response.recording !== "object" || Array.isArray(response.recording)) {
3128
+ return false;
3129
+ }
3130
+ const recording = response.recording;
3131
+ return typeof recording.id === "string" && typeof recording.workspaceId === "string" && typeof recording.mimeType === "string" && typeof recording.state === "string" && typeof recording.nextChunkNumber === "number" && typeof recording.chunkCount === "number" && typeof recording.totalBytes === "number" && typeof recording.totalDurationMilliseconds === "number" && typeof recording.segmentCount === "number" && typeof recording.completedSegmentCount === "number" && (recording.transcriptText === null || typeof recording.transcriptText === "string") && Array.isArray(recording.languages) && (recording.errorCode === null || typeof recording.errorCode === "string") && typeof recording.retryable === "boolean" && typeof recording.objectsCleaned === "boolean" && typeof recording.createdAt === "string" && typeof recording.updatedAt === "string" && typeof recording.expiresAt === "string" && (response.retryAfterMilliseconds === void 0 || typeof response.retryAfterMilliseconds === "number" && Number.isInteger(response.retryAfterMilliseconds) && response.retryAfterMilliseconds > 0 && response.retryAfterMilliseconds <= 6e4) && Array.isArray(response.segments);
3132
+ }
3133
+ function transcriptionRecordingResponse(value) {
3134
+ if (isTranscriptionRecordingResponse(value)) return value;
3135
+ throw new OpenGeniApiError(502, "Invalid transcription recording response.", {
3136
+ code: "invalid_response"
3137
+ });
3138
+ }
3139
+ function isTranscriptionRecordingListResponse(value) {
3140
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3141
+ const recordings = value.recordings;
3142
+ return Array.isArray(recordings) && recordings.length <= 50 && recordings.every((recording) => isTranscriptionRecordingResponse({ recording, segments: [] }));
3143
+ }
2730
3144
  function filenameForAudioMimeType(mimeType) {
2731
3145
  const bare = mimeType.trim().toLowerCase().split(";")[0] ?? "audio/webm";
2732
3146
  switch (bare) {
@@ -2802,6 +3216,11 @@ async function sha256ForUpload(body) {
2802
3216
  const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2803
3217
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2804
3218
  }
3219
+ async function sha256Hex(bytes) {
3220
+ const owned = Uint8Array.from(bytes);
3221
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", owned.buffer);
3222
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
3223
+ }
2805
3224
  async function cancelResponseBody(response, reason) {
2806
3225
  await response.body?.cancel(reason).catch(() => void 0);
2807
3226
  }
@@ -3094,4 +3513,4 @@ export {
3094
3513
  authorizeTranscriptionAdapter,
3095
3514
  createTranscriptionSessionRequest
3096
3515
  };
3097
- //# sourceMappingURL=chunk-ONKUBP7A.js.map
3516
+ //# sourceMappingURL=chunk-MBGBLVUV.js.map