@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.
- package/README.md +1 -1
- package/dist/{chunk-ONKUBP7A.js → chunk-MBGBLVUV.js} +489 -70
- package/dist/chunk-MBGBLVUV.js.map +1 -0
- package/dist/client.d.ts +61 -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 +139 -10
- package/dist/workspace-control-stream.d.ts +1 -1
- package/package.json +1 -1
- package/src/client.ts +594 -58
- package/src/index.ts +9 -0
- package/src/stream.ts +102 -4
- package/src/transcription.ts +2 -2
- package/src/types.ts +181 -7
- package/src/workspace-control-stream.ts +15 -5
- package/dist/chunk-ONKUBP7A.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,
|
|
@@ -189,6 +197,9 @@ import type {
|
|
|
189
197
|
PtyCloseRequest,
|
|
190
198
|
ToolRef,
|
|
191
199
|
TranscribeAudioResponse,
|
|
200
|
+
TranscriptionRecordingListResponse,
|
|
201
|
+
TranscriptionRecordingResponse,
|
|
202
|
+
UploadTranscriptionRecordingChunkResponse,
|
|
192
203
|
UpdateConnectionRequest,
|
|
193
204
|
UpdateKnowledgeMemoryRequest,
|
|
194
205
|
UpdateScheduledTaskRequest,
|
|
@@ -203,6 +214,7 @@ import type {
|
|
|
203
214
|
SetWorkspaceDefaultRigRequest,
|
|
204
215
|
UploadFileInput,
|
|
205
216
|
VariableSet,
|
|
217
|
+
VariableSetSecret,
|
|
206
218
|
VariableSetVariableMetadata,
|
|
207
219
|
Rig,
|
|
208
220
|
RigVersion,
|
|
@@ -261,6 +273,7 @@ import {
|
|
|
261
273
|
OPENGENI_API_CONTRACT_HEADER,
|
|
262
274
|
OPENGENI_API_CONTRACT_REVISION,
|
|
263
275
|
OPENGENI_CORRELATION_HEADER,
|
|
276
|
+
COMPUTER_SCREENSHOT_MAX_BYTES,
|
|
264
277
|
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
265
278
|
} from "./types";
|
|
266
279
|
|
|
@@ -329,6 +342,28 @@ export type TranscribeAudioInput = {
|
|
|
329
342
|
signal?: AbortSignal | undefined;
|
|
330
343
|
};
|
|
331
344
|
|
|
345
|
+
export type CreateTranscriptionRecordingInput = {
|
|
346
|
+
recordingId: string;
|
|
347
|
+
mimeType: string;
|
|
348
|
+
signal?: AbortSignal | undefined;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export type UploadTranscriptionRecordingChunkInput = {
|
|
352
|
+
audio: Blob | File | Uint8Array;
|
|
353
|
+
mimeType: string;
|
|
354
|
+
sha256: string;
|
|
355
|
+
startMilliseconds: number;
|
|
356
|
+
durationMilliseconds: number;
|
|
357
|
+
signal?: AbortSignal | undefined;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export type FinalizeTranscriptionRecordingInput = {
|
|
361
|
+
chunkCount: number;
|
|
362
|
+
totalBytes: number;
|
|
363
|
+
totalDurationMilliseconds: number;
|
|
364
|
+
signal?: AbortSignal | undefined;
|
|
365
|
+
};
|
|
366
|
+
|
|
332
367
|
/**
|
|
333
368
|
* Typed client for the OpenGeni public API. Framework-agnostic: only needs
|
|
334
369
|
* WHATWG `fetch` + streams, so it runs in Node 18+, Bun, Deno, browsers, and
|
|
@@ -338,6 +373,8 @@ export class OpenGeniClient {
|
|
|
338
373
|
private readonly baseUrl: string;
|
|
339
374
|
private readonly options: OpenGeniClientOptions;
|
|
340
375
|
private readonly fetchImpl: FetchLike;
|
|
376
|
+
private readonly readInFlight = new Map<string, Promise<unknown>>();
|
|
377
|
+
private readonly readTrailing = new Map<string, Promise<unknown>>();
|
|
341
378
|
|
|
342
379
|
constructor(options: OpenGeniClientOptions) {
|
|
343
380
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
@@ -360,8 +397,12 @@ export class OpenGeniClient {
|
|
|
360
397
|
input.audio instanceof File
|
|
361
398
|
? input.audio
|
|
362
399
|
: input.audio instanceof Uint8Array
|
|
363
|
-
? new File([Uint8Array.from(input.audio)], filename, {
|
|
364
|
-
|
|
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
|
+
});
|
|
365
406
|
form.append("audio", audio, filename);
|
|
366
407
|
form.append("mimeType", input.mimeType);
|
|
367
408
|
if (input.durationSeconds !== undefined) {
|
|
@@ -371,7 +412,10 @@ export class OpenGeniClient {
|
|
|
371
412
|
try {
|
|
372
413
|
response = await this.fetchImpl(this.url(`/v1/workspaces/${workspaceId}/transcriptions`), {
|
|
373
414
|
method: "POST",
|
|
374
|
-
headers: {
|
|
415
|
+
headers: {
|
|
416
|
+
...this.headers(correlationId),
|
|
417
|
+
Accept: "application/json",
|
|
418
|
+
},
|
|
375
419
|
body: form,
|
|
376
420
|
...(input.signal ? { signal: input.signal } : {}),
|
|
377
421
|
});
|
|
@@ -380,7 +424,11 @@ export class OpenGeniClient {
|
|
|
380
424
|
throw mutationTransportError(correlationId);
|
|
381
425
|
}
|
|
382
426
|
assertApiContractResponse(response);
|
|
383
|
-
if (!response.ok)
|
|
427
|
+
if (!response.ok)
|
|
428
|
+
throw await apiErrorFromResponse(response, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
correlationId,
|
|
431
|
+
});
|
|
384
432
|
await assertJsonResponse(response, { method: "POST", correlationId });
|
|
385
433
|
let body: unknown;
|
|
386
434
|
try {
|
|
@@ -402,6 +450,157 @@ export class OpenGeniClient {
|
|
|
402
450
|
return body;
|
|
403
451
|
}
|
|
404
452
|
|
|
453
|
+
async createTranscriptionRecording(
|
|
454
|
+
workspaceId: string,
|
|
455
|
+
input: CreateTranscriptionRecordingInput,
|
|
456
|
+
): Promise<TranscriptionRecordingResponse> {
|
|
457
|
+
return transcriptionRecordingResponse(
|
|
458
|
+
await this.requestJson<unknown>(
|
|
459
|
+
"POST",
|
|
460
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings`,
|
|
461
|
+
{ recordingId: input.recordingId, mimeType: input.mimeType },
|
|
462
|
+
{},
|
|
463
|
+
input.signal ? { signal: input.signal } : {},
|
|
464
|
+
),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async getTranscriptionRecording(
|
|
469
|
+
workspaceId: string,
|
|
470
|
+
recordingId: string,
|
|
471
|
+
options: { signal?: AbortSignal | undefined } = {},
|
|
472
|
+
): Promise<TranscriptionRecordingResponse> {
|
|
473
|
+
return transcriptionRecordingResponse(
|
|
474
|
+
await this.requestJson<unknown>(
|
|
475
|
+
"GET",
|
|
476
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}`,
|
|
477
|
+
undefined,
|
|
478
|
+
{},
|
|
479
|
+
options.signal ? { signal: options.signal } : {},
|
|
480
|
+
),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async listTranscriptionRecordings(
|
|
485
|
+
workspaceId: string,
|
|
486
|
+
options: { signal?: AbortSignal | undefined } = {},
|
|
487
|
+
): Promise<TranscriptionRecordingListResponse> {
|
|
488
|
+
const body = await this.requestJson<unknown>(
|
|
489
|
+
"GET",
|
|
490
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings`,
|
|
491
|
+
undefined,
|
|
492
|
+
{},
|
|
493
|
+
options.signal ? { signal: options.signal } : {},
|
|
494
|
+
);
|
|
495
|
+
if (!isTranscriptionRecordingListResponse(body)) {
|
|
496
|
+
throw new OpenGeniApiError(502, "Invalid transcription recording list response.", {
|
|
497
|
+
code: "invalid_response",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return body;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async uploadTranscriptionRecordingChunk(
|
|
504
|
+
workspaceId: string,
|
|
505
|
+
recordingId: string,
|
|
506
|
+
chunkNumber: number,
|
|
507
|
+
input: UploadTranscriptionRecordingChunkInput,
|
|
508
|
+
): Promise<UploadTranscriptionRecordingChunkResponse> {
|
|
509
|
+
const correlationId = crypto.randomUUID();
|
|
510
|
+
let response: Response;
|
|
511
|
+
try {
|
|
512
|
+
response = await this.fetchImpl(
|
|
513
|
+
this.url(
|
|
514
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/chunks/${chunkNumber}`,
|
|
515
|
+
),
|
|
516
|
+
{
|
|
517
|
+
method: "PUT",
|
|
518
|
+
headers: {
|
|
519
|
+
...this.headers(correlationId),
|
|
520
|
+
Accept: "application/json",
|
|
521
|
+
"Content-Type": input.mimeType,
|
|
522
|
+
"x-opengeni-chunk-sha256": input.sha256,
|
|
523
|
+
"x-opengeni-chunk-start-milliseconds": String(input.startMilliseconds),
|
|
524
|
+
"x-opengeni-chunk-duration-milliseconds": String(input.durationMilliseconds),
|
|
525
|
+
},
|
|
526
|
+
body: input.audio instanceof Uint8Array ? Uint8Array.from(input.audio) : input.audio,
|
|
527
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
528
|
+
},
|
|
529
|
+
);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (input.signal?.aborted) throw error;
|
|
532
|
+
throw mutationTransportError(correlationId);
|
|
533
|
+
}
|
|
534
|
+
assertApiContractResponse(response);
|
|
535
|
+
if (!response.ok)
|
|
536
|
+
throw await apiErrorFromResponse(response, {
|
|
537
|
+
method: "PUT",
|
|
538
|
+
correlationId,
|
|
539
|
+
});
|
|
540
|
+
await assertJsonResponse(response, { method: "PUT", correlationId });
|
|
541
|
+
const body = await response.json().catch(() => null);
|
|
542
|
+
if (!isUploadTranscriptionRecordingChunkResponse(body)) {
|
|
543
|
+
throw new OpenGeniApiError(response.status, "Invalid transcription chunk response.", {
|
|
544
|
+
code: "invalid_response",
|
|
545
|
+
mutation: true,
|
|
546
|
+
correlationId,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return body;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async finalizeTranscriptionRecording(
|
|
553
|
+
workspaceId: string,
|
|
554
|
+
recordingId: string,
|
|
555
|
+
input: FinalizeTranscriptionRecordingInput,
|
|
556
|
+
): Promise<TranscriptionRecordingResponse> {
|
|
557
|
+
return transcriptionRecordingResponse(
|
|
558
|
+
await this.requestJson<unknown>(
|
|
559
|
+
"POST",
|
|
560
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/finalize`,
|
|
561
|
+
{
|
|
562
|
+
chunkCount: input.chunkCount,
|
|
563
|
+
totalBytes: input.totalBytes,
|
|
564
|
+
totalDurationMilliseconds: input.totalDurationMilliseconds,
|
|
565
|
+
},
|
|
566
|
+
{},
|
|
567
|
+
input.signal ? { signal: input.signal } : {},
|
|
568
|
+
),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async processNextTranscriptionRecordingSegment(
|
|
573
|
+
workspaceId: string,
|
|
574
|
+
recordingId: string,
|
|
575
|
+
options: { signal?: AbortSignal | undefined } = {},
|
|
576
|
+
): Promise<TranscriptionRecordingResponse> {
|
|
577
|
+
return transcriptionRecordingResponse(
|
|
578
|
+
await this.requestJson<unknown>(
|
|
579
|
+
"POST",
|
|
580
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}/process-next`,
|
|
581
|
+
{},
|
|
582
|
+
{},
|
|
583
|
+
options.signal ? { signal: options.signal } : {},
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async discardTranscriptionRecording(
|
|
589
|
+
workspaceId: string,
|
|
590
|
+
recordingId: string,
|
|
591
|
+
options: { signal?: AbortSignal | undefined } = {},
|
|
592
|
+
): Promise<TranscriptionRecordingResponse> {
|
|
593
|
+
return transcriptionRecordingResponse(
|
|
594
|
+
await this.requestJson<unknown>(
|
|
595
|
+
"DELETE",
|
|
596
|
+
`/v1/workspaces/${workspaceId}/transcription-recordings/${recordingId}`,
|
|
597
|
+
undefined,
|
|
598
|
+
{},
|
|
599
|
+
options.signal ? { signal: options.signal } : {},
|
|
600
|
+
),
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
405
604
|
async createSession(
|
|
406
605
|
workspaceId: string,
|
|
407
606
|
request: CreateSessionRequest,
|
|
@@ -431,11 +630,13 @@ export class OpenGeniClient {
|
|
|
431
630
|
);
|
|
432
631
|
}
|
|
433
632
|
|
|
434
|
-
async getSession(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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);
|
|
439
640
|
}
|
|
440
641
|
|
|
441
642
|
async updateSession(
|
|
@@ -582,12 +783,40 @@ export class OpenGeniClient {
|
|
|
582
783
|
}
|
|
583
784
|
|
|
584
785
|
async getSessionLineage(workspaceId: string, sessionId: string): Promise<SessionLineageResponse> {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
786
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
|
|
787
|
+
return await this.singleFlightRead(path, () =>
|
|
788
|
+
this.requestJson<SessionLineageResponse>("GET", path),
|
|
588
789
|
);
|
|
589
790
|
}
|
|
590
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
|
+
|
|
591
820
|
/** Negotiate one server-mediated connected-Codex GPT-Live V3 WebRTC call. */
|
|
592
821
|
async negotiateCodexRealtimeWebrtc(
|
|
593
822
|
workspaceId: string,
|
|
@@ -711,7 +940,9 @@ export class OpenGeniClient {
|
|
|
711
940
|
|
|
712
941
|
/** Newest turn that durably emitted `turn.started`, or null before any admission. */
|
|
713
942
|
async getLatestStartedTurn(workspaceId: string, sessionId: string): Promise<SessionTurn | null> {
|
|
714
|
-
const turns = await this.listTurns(workspaceId, sessionId, {
|
|
943
|
+
const turns = await this.listTurns(workspaceId, sessionId, {
|
|
944
|
+
latestStarted: true,
|
|
945
|
+
});
|
|
715
946
|
return turns[0] ?? null;
|
|
716
947
|
}
|
|
717
948
|
|
|
@@ -756,6 +987,24 @@ export class OpenGeniClient {
|
|
|
756
987
|
return response.samples;
|
|
757
988
|
}
|
|
758
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
|
+
|
|
759
1008
|
// --- Self-hosted enrollment UX (design 11) --------------------------------
|
|
760
1009
|
|
|
761
1010
|
/**
|
|
@@ -1178,9 +1427,9 @@ export class OpenGeniClient {
|
|
|
1178
1427
|
// --- Turn queue ------------------------------------------------------------
|
|
1179
1428
|
|
|
1180
1429
|
async getQueue(workspaceId: string, sessionId: string): Promise<SessionQueueSnapshot> {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1430
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`;
|
|
1431
|
+
return await this.singleFlightRead(path, () =>
|
|
1432
|
+
this.requestJson<SessionQueueSnapshot>("GET", path),
|
|
1184
1433
|
);
|
|
1185
1434
|
}
|
|
1186
1435
|
|
|
@@ -1297,7 +1546,11 @@ export class OpenGeniClient {
|
|
|
1297
1546
|
async cancelSession(
|
|
1298
1547
|
workspaceId: string,
|
|
1299
1548
|
sessionId: string,
|
|
1300
|
-
options: {
|
|
1549
|
+
options: {
|
|
1550
|
+
reason?: string;
|
|
1551
|
+
clientEventId?: string;
|
|
1552
|
+
expectedControlEtag?: string;
|
|
1553
|
+
} = {},
|
|
1301
1554
|
): Promise<SessionControlResponse> {
|
|
1302
1555
|
return await this.controlSession(workspaceId, sessionId, {
|
|
1303
1556
|
action: "cancel",
|
|
@@ -1442,10 +1695,8 @@ export class OpenGeniClient {
|
|
|
1442
1695
|
|
|
1443
1696
|
/** The session's goal. 404s when the session never had one. */
|
|
1444
1697
|
async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
|
|
1448
|
-
);
|
|
1698
|
+
const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
|
|
1699
|
+
return await this.singleFlightRead(path, () => this.requestJson<SessionGoal>("GET", path));
|
|
1449
1700
|
}
|
|
1450
1701
|
|
|
1451
1702
|
async updateGoal(
|
|
@@ -1531,6 +1782,22 @@ export class OpenGeniClient {
|
|
|
1531
1782
|
);
|
|
1532
1783
|
}
|
|
1533
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
|
+
|
|
1534
1801
|
/** FileSystem: read a file (text or base64; binary-safe, size-capped). */
|
|
1535
1802
|
async fsRead(
|
|
1536
1803
|
workspaceId: string,
|
|
@@ -1631,6 +1898,22 @@ export class OpenGeniClient {
|
|
|
1631
1898
|
);
|
|
1632
1899
|
}
|
|
1633
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
|
+
|
|
1634
1917
|
/** Git: commit log. */
|
|
1635
1918
|
async gitLog(
|
|
1636
1919
|
workspaceId: string,
|
|
@@ -2312,7 +2595,8 @@ export class OpenGeniClient {
|
|
|
2312
2595
|
}
|
|
2313
2596
|
|
|
2314
2597
|
// --- VariableSets --------------------------------------------------------------
|
|
2315
|
-
//
|
|
2598
|
+
// Generic reads return name/version metadata only. Plaintext uses one
|
|
2599
|
+
// dedicated permissioned endpoint.
|
|
2316
2600
|
|
|
2317
2601
|
async listVariableSets(workspaceId: string): Promise<VariableSet[]> {
|
|
2318
2602
|
return await this.requestJson<VariableSet[]>(
|
|
@@ -2339,6 +2623,17 @@ export class OpenGeniClient {
|
|
|
2339
2623
|
);
|
|
2340
2624
|
}
|
|
2341
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
|
+
|
|
2342
2637
|
async updateVariableSet(
|
|
2343
2638
|
workspaceId: string,
|
|
2344
2639
|
variableSetId: string,
|
|
@@ -2358,7 +2653,7 @@ export class OpenGeniClient {
|
|
|
2358
2653
|
);
|
|
2359
2654
|
}
|
|
2360
2655
|
|
|
2361
|
-
/** Create or rotate a variable.
|
|
2656
|
+
/** Create or rotate a variable. Generic reads never return its value. */
|
|
2362
2657
|
async setVariableSetVariable(
|
|
2363
2658
|
workspaceId: string,
|
|
2364
2659
|
variableSetId: string,
|
|
@@ -2554,19 +2849,29 @@ export class OpenGeniClient {
|
|
|
2554
2849
|
async beginFileUpload(
|
|
2555
2850
|
workspaceId: string,
|
|
2556
2851
|
request: CreateFileUploadRequest,
|
|
2852
|
+
options: OpenGeniRequestOptions = {},
|
|
2557
2853
|
): Promise<CreateFileUploadResponse> {
|
|
2558
2854
|
return await this.requestJson<CreateFileUploadResponse>(
|
|
2559
2855
|
"POST",
|
|
2560
2856
|
`/v1/workspaces/${workspaceId}/files/uploads`,
|
|
2561
2857
|
request,
|
|
2858
|
+
{},
|
|
2859
|
+
options,
|
|
2562
2860
|
);
|
|
2563
2861
|
}
|
|
2564
2862
|
|
|
2565
2863
|
/** Step 3 of the upload flow: server verifies the object and marks it ready. */
|
|
2566
|
-
async completeFileUpload(
|
|
2864
|
+
async completeFileUpload(
|
|
2865
|
+
workspaceId: string,
|
|
2866
|
+
uploadId: string,
|
|
2867
|
+
options: OpenGeniRequestOptions = {},
|
|
2868
|
+
): Promise<FileAsset> {
|
|
2567
2869
|
const response = await this.requestJson<CompleteFileUploadResponse>(
|
|
2568
2870
|
"POST",
|
|
2569
2871
|
`/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`,
|
|
2872
|
+
undefined,
|
|
2873
|
+
{},
|
|
2874
|
+
options,
|
|
2570
2875
|
);
|
|
2571
2876
|
return response.file;
|
|
2572
2877
|
}
|
|
@@ -2577,6 +2882,30 @@ export class OpenGeniClient {
|
|
|
2577
2882
|
* -> complete. Returns the ready `FileAsset`.
|
|
2578
2883
|
*/
|
|
2579
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
|
+
};
|
|
2580
2909
|
// Snapshot mutable inputs before hashing so the digest always describes the
|
|
2581
2910
|
// exact bytes later sent to object storage. Copy Uint8Array views into a
|
|
2582
2911
|
// Blob so byte offsets/shared buffers can't leak surrounding bytes.
|
|
@@ -2593,32 +2922,52 @@ export class OpenGeniClient {
|
|
|
2593
2922
|
? body.size
|
|
2594
2923
|
: body.byteLength;
|
|
2595
2924
|
const sha256 = input.sha256 ?? (await sha256ForUpload(body));
|
|
2596
|
-
const upload = await
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
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
|
+
);
|
|
2618
2964
|
if (!putResponse.ok) {
|
|
2619
2965
|
throw await apiErrorFromResponse(putResponse, { method: "PUT" });
|
|
2620
2966
|
}
|
|
2621
|
-
return await
|
|
2967
|
+
return await withTimeout(
|
|
2968
|
+
30_000,
|
|
2969
|
+
async (signal) => await this.completeFileUpload(workspaceId, upload.uploadId, { signal }),
|
|
2970
|
+
);
|
|
2622
2971
|
}
|
|
2623
2972
|
|
|
2624
2973
|
async getFile(workspaceId: string, fileId: string): Promise<FileAsset> {
|
|
@@ -2647,23 +2996,119 @@ export class OpenGeniClient {
|
|
|
2647
2996
|
workspaceId: string,
|
|
2648
2997
|
artifactId: string,
|
|
2649
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,
|
|
2650
3098
|
): Promise<RetainedArtifactContent> {
|
|
2651
3099
|
if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
|
|
2652
3100
|
throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
|
|
2653
3101
|
}
|
|
2654
3102
|
const correlationId = crypto.randomUUID();
|
|
2655
|
-
const response = await this.fetchImpl(
|
|
2656
|
-
|
|
2657
|
-
{
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
Accept: "application/octet-stream",
|
|
2662
|
-
...(options.range ? { Range: options.range } : {}),
|
|
2663
|
-
},
|
|
2664
|
-
...(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 } : {}),
|
|
2665
3109
|
},
|
|
2666
|
-
|
|
3110
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
3111
|
+
});
|
|
2667
3112
|
try {
|
|
2668
3113
|
assertApiContractResponse(response);
|
|
2669
3114
|
} catch (error) {
|
|
@@ -3595,6 +4040,91 @@ function isTranscribeAudioResponse(value: unknown): value is TranscribeAudioResp
|
|
|
3595
4040
|
);
|
|
3596
4041
|
}
|
|
3597
4042
|
|
|
4043
|
+
function isUploadTranscriptionRecordingChunkResponse(
|
|
4044
|
+
value: unknown,
|
|
4045
|
+
): value is UploadTranscriptionRecordingChunkResponse {
|
|
4046
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4047
|
+
const record = value as Record<string, unknown>;
|
|
4048
|
+
if (
|
|
4049
|
+
!isTranscriptionRecordingResponse({
|
|
4050
|
+
recording: record.recording,
|
|
4051
|
+
segments: [],
|
|
4052
|
+
})
|
|
4053
|
+
) {
|
|
4054
|
+
return false;
|
|
4055
|
+
}
|
|
4056
|
+
if (!record.chunk || typeof record.chunk !== "object" || Array.isArray(record.chunk)) {
|
|
4057
|
+
return false;
|
|
4058
|
+
}
|
|
4059
|
+
const chunk = record.chunk as Record<string, unknown>;
|
|
4060
|
+
return (
|
|
4061
|
+
typeof chunk.chunkNumber === "number" &&
|
|
4062
|
+
typeof chunk.byteLength === "number" &&
|
|
4063
|
+
typeof chunk.sha256 === "string" &&
|
|
4064
|
+
typeof chunk.startMilliseconds === "number" &&
|
|
4065
|
+
typeof chunk.durationMilliseconds === "number" &&
|
|
4066
|
+
typeof chunk.deduplicated === "boolean"
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
function isTranscriptionRecordingResponse(value: unknown): value is TranscriptionRecordingResponse {
|
|
4071
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4072
|
+
const response = value as Record<string, unknown>;
|
|
4073
|
+
if (
|
|
4074
|
+
!response.recording ||
|
|
4075
|
+
typeof response.recording !== "object" ||
|
|
4076
|
+
Array.isArray(response.recording)
|
|
4077
|
+
) {
|
|
4078
|
+
return false;
|
|
4079
|
+
}
|
|
4080
|
+
const recording = response.recording as Record<string, unknown>;
|
|
4081
|
+
return (
|
|
4082
|
+
typeof recording.id === "string" &&
|
|
4083
|
+
typeof recording.workspaceId === "string" &&
|
|
4084
|
+
typeof recording.mimeType === "string" &&
|
|
4085
|
+
typeof recording.state === "string" &&
|
|
4086
|
+
typeof recording.nextChunkNumber === "number" &&
|
|
4087
|
+
typeof recording.chunkCount === "number" &&
|
|
4088
|
+
typeof recording.totalBytes === "number" &&
|
|
4089
|
+
typeof recording.totalDurationMilliseconds === "number" &&
|
|
4090
|
+
typeof recording.segmentCount === "number" &&
|
|
4091
|
+
typeof recording.completedSegmentCount === "number" &&
|
|
4092
|
+
(recording.transcriptText === null || typeof recording.transcriptText === "string") &&
|
|
4093
|
+
Array.isArray(recording.languages) &&
|
|
4094
|
+
(recording.errorCode === null || typeof recording.errorCode === "string") &&
|
|
4095
|
+
typeof recording.retryable === "boolean" &&
|
|
4096
|
+
typeof recording.objectsCleaned === "boolean" &&
|
|
4097
|
+
typeof recording.createdAt === "string" &&
|
|
4098
|
+
typeof recording.updatedAt === "string" &&
|
|
4099
|
+
typeof recording.expiresAt === "string" &&
|
|
4100
|
+
(response.retryAfterMilliseconds === undefined ||
|
|
4101
|
+
(typeof response.retryAfterMilliseconds === "number" &&
|
|
4102
|
+
Number.isInteger(response.retryAfterMilliseconds) &&
|
|
4103
|
+
response.retryAfterMilliseconds > 0 &&
|
|
4104
|
+
response.retryAfterMilliseconds <= 60_000)) &&
|
|
4105
|
+
Array.isArray(response.segments)
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
function transcriptionRecordingResponse(value: unknown): TranscriptionRecordingResponse {
|
|
4110
|
+
if (isTranscriptionRecordingResponse(value)) return value;
|
|
4111
|
+
throw new OpenGeniApiError(502, "Invalid transcription recording response.", {
|
|
4112
|
+
code: "invalid_response",
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
function isTranscriptionRecordingListResponse(
|
|
4117
|
+
value: unknown,
|
|
4118
|
+
): value is TranscriptionRecordingListResponse {
|
|
4119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4120
|
+
const recordings = (value as Record<string, unknown>).recordings;
|
|
4121
|
+
return (
|
|
4122
|
+
Array.isArray(recordings) &&
|
|
4123
|
+
recordings.length <= 50 &&
|
|
4124
|
+
recordings.every((recording) => isTranscriptionRecordingResponse({ recording, segments: [] }))
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
|
|
3598
4128
|
function filenameForAudioMimeType(mimeType: string): string {
|
|
3599
4129
|
const bare = mimeType.trim().toLowerCase().split(";")[0] ?? "audio/webm";
|
|
3600
4130
|
switch (bare) {
|
|
@@ -3695,6 +4225,12 @@ async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<strin
|
|
|
3695
4225
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3696
4226
|
}
|
|
3697
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
|
+
|
|
3698
4234
|
async function cancelResponseBody(response: Response, reason: string): Promise<void> {
|
|
3699
4235
|
await response.body?.cancel(reason).catch(() => undefined);
|
|
3700
4236
|
}
|