@opengeni/sdk 0.15.0 → 0.20.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 +33 -6
- package/dist/index.d.ts +442 -29
- package/dist/index.js +379 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +222 -23
- package/src/index.ts +51 -0
- package/src/transcription.ts +496 -0
- package/src/types.ts +330 -23
package/dist/index.js
CHANGED
|
@@ -293,8 +293,11 @@ async function sleep2(delayMs, signal) {
|
|
|
293
293
|
// src/types.ts
|
|
294
294
|
var SESSION_EVENT_TYPES = [
|
|
295
295
|
"session.created",
|
|
296
|
+
// Defensive bounded projection for malformed/legacy oversized envelopes.
|
|
297
|
+
"session.event.envelope_omitted",
|
|
296
298
|
"session.status.changed",
|
|
297
299
|
"session.requiresAction",
|
|
300
|
+
"session.humanInput.requested",
|
|
298
301
|
"session.context.compaction.requested",
|
|
299
302
|
"session.context.compacted",
|
|
300
303
|
"session.context.compaction.skipped",
|
|
@@ -302,6 +305,7 @@ var SESSION_EVENT_TYPES = [
|
|
|
302
305
|
"user.message",
|
|
303
306
|
"user.pause",
|
|
304
307
|
"user.approvalDecision",
|
|
308
|
+
"user.humanInputResponse",
|
|
305
309
|
"turn.queued",
|
|
306
310
|
"turn.started",
|
|
307
311
|
"turn.completed",
|
|
@@ -317,6 +321,7 @@ var SESSION_EVENT_TYPES = [
|
|
|
317
321
|
"agent.toolCall.output",
|
|
318
322
|
"agent.model.usage",
|
|
319
323
|
"tool.auth_needed",
|
|
324
|
+
"credential.auth_needed",
|
|
320
325
|
"agent.updated",
|
|
321
326
|
"rig.setup.started",
|
|
322
327
|
"rig.setup.completed",
|
|
@@ -367,9 +372,9 @@ var SESSION_EVENT_TYPES = [
|
|
|
367
372
|
"session.title_set",
|
|
368
373
|
// Multi-account Codex (P1): the session's inference account changed.
|
|
369
374
|
"codex.account.switched",
|
|
370
|
-
//
|
|
375
|
+
// credential allocator metadata-only per-turn credential selection audit.
|
|
371
376
|
"codex.credential.selected",
|
|
372
|
-
//
|
|
377
|
+
// credential allocator durable zero-capacity wait lifecycle. These are system/runtime
|
|
373
378
|
// events, never synthetic user messages.
|
|
374
379
|
"codex.capacity.waiting",
|
|
375
380
|
"codex.capacity.resumed",
|
|
@@ -410,7 +415,7 @@ var KNOWN_PERMISSIONS = [
|
|
|
410
415
|
"sessions:create",
|
|
411
416
|
"sessions:read",
|
|
412
417
|
"sessions:control",
|
|
413
|
-
//
|
|
418
|
+
// sandbox workspace (mirror of @opengeni/contracts Permission). stream:view is
|
|
414
419
|
// strictly broader than sessions:read (un-redacted pixels); stream:control is
|
|
415
420
|
// the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
|
|
416
421
|
// consent gate.
|
|
@@ -442,7 +447,7 @@ var KNOWN_PERMISSIONS = [
|
|
|
442
447
|
"rigs:use",
|
|
443
448
|
"rigs:manage"
|
|
444
449
|
];
|
|
445
|
-
var OPENGENI_API_CONTRACT_REVISION = "2026-07-
|
|
450
|
+
var OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1";
|
|
446
451
|
var OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract";
|
|
447
452
|
var KNOWN_USAGE_EVENT_TYPES = [
|
|
448
453
|
"agent_run.created",
|
|
@@ -559,7 +564,8 @@ var OpenGeniClient = class {
|
|
|
559
564
|
void 0,
|
|
560
565
|
{
|
|
561
566
|
...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {}
|
|
562
|
-
}
|
|
567
|
+
},
|
|
568
|
+
{ signal: options.signal }
|
|
563
569
|
);
|
|
564
570
|
}
|
|
565
571
|
/**
|
|
@@ -659,23 +665,75 @@ var OpenGeniClient = class {
|
|
|
659
665
|
}
|
|
660
666
|
// --- Events: replay, send, stream ----------------------------------------
|
|
661
667
|
/**
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
668
|
+
* Return the events from one bounded page. With no cursor, this uses the safe
|
|
669
|
+
* semantic monitoring tail; pass explicit forensic options and a cursor for
|
|
670
|
+
* retained audit replay. Use `listEventPage` when projection, coverage, or
|
|
671
|
+
* resume-cursor facts are required.
|
|
666
672
|
*/
|
|
667
673
|
async listEvents(workspaceId, sessionId, options = {}) {
|
|
668
|
-
return await this.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
674
|
+
return (await this.listEventPage(workspaceId, sessionId, options)).events;
|
|
675
|
+
}
|
|
676
|
+
/** Bounded durable/monitoring page plus exact projection and cursor facts. */
|
|
677
|
+
async listEventPage(workspaceId, sessionId, options = {}) {
|
|
678
|
+
if (options.latest && ["includeTypes", "excludeTypes", "includeClasses", "excludeClasses"].some(
|
|
679
|
+
(name) => Object.prototype.hasOwnProperty.call(options, name)
|
|
680
|
+
)) {
|
|
681
|
+
throw new TypeError("latest cannot be combined with event filters");
|
|
682
|
+
}
|
|
683
|
+
const response = await this.fetchImpl(
|
|
684
|
+
this.url(`/v1/workspaces/${workspaceId}/sessions/${sessionId}/events`, {
|
|
673
685
|
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
674
686
|
...options.before !== void 0 ? { before: String(options.before) } : {},
|
|
675
687
|
...options.limit !== void 0 ? { limit: String(options.limit) } : {},
|
|
676
|
-
...options.compact ? { compact: "1" } : {}
|
|
688
|
+
...options.compact ? { compact: "1" } : {},
|
|
689
|
+
...options.mode ? { mode: options.mode } : {},
|
|
690
|
+
...options.direction ? { direction: options.direction } : {},
|
|
691
|
+
...options.payloadMode ? { payloadMode: options.payloadMode } : {},
|
|
692
|
+
...options.includeTypes?.length ? { includeTypes: options.includeTypes.join(",") } : {},
|
|
693
|
+
...options.excludeTypes?.length ? { excludeTypes: options.excludeTypes.join(",") } : {},
|
|
694
|
+
...options.includeClasses?.length ? { includeClasses: options.includeClasses.join(",") } : {},
|
|
695
|
+
...options.excludeClasses?.length ? { excludeClasses: options.excludeClasses.join(",") } : {},
|
|
696
|
+
...options.latest ? { latest: options.latest } : {}
|
|
697
|
+
}),
|
|
698
|
+
{
|
|
699
|
+
method: "GET",
|
|
700
|
+
headers: { ...this.headers(), Accept: "application/json" }
|
|
677
701
|
}
|
|
678
702
|
);
|
|
703
|
+
assertApiContractResponse(response);
|
|
704
|
+
if (!response.ok) throw new OpenGeniApiError(response.status, await safeText(response));
|
|
705
|
+
const events = await response.json();
|
|
706
|
+
const integerHeader = (name) => {
|
|
707
|
+
const raw = response.headers.get(name);
|
|
708
|
+
if (raw === null) return null;
|
|
709
|
+
const value = Number(raw);
|
|
710
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
711
|
+
};
|
|
712
|
+
const mode = response.headers.get("X-OpenGeni-Event-Mode") === "forensic" ? "forensic" : "monitoring";
|
|
713
|
+
const direction = response.headers.get("X-OpenGeni-Event-Direction") === "after" ? "after" : "before";
|
|
714
|
+
const payloadHeader = response.headers.get("X-OpenGeni-Payload-Mode");
|
|
715
|
+
const payloadMode = payloadHeader === "none" || payloadHeader === "full" ? payloadHeader : "summary";
|
|
716
|
+
const first = integerHeader("X-OpenGeni-Covered-First");
|
|
717
|
+
const last = integerHeader("X-OpenGeni-Covered-Last");
|
|
718
|
+
const bytes = integerHeader("X-OpenGeni-Page-Bytes") ?? new TextEncoder().encode(JSON.stringify(events)).byteLength;
|
|
719
|
+
const maxBytes = integerHeader("X-OpenGeni-Page-Max-Bytes") ?? 1024 * 1024;
|
|
720
|
+
const truncatedByHeader = response.headers.get("X-OpenGeni-Truncated-By");
|
|
721
|
+
const truncatedBy = truncatedByHeader === "count" || truncatedByHeader === "bytes" || truncatedByHeader === "http_bytes" ? truncatedByHeader : null;
|
|
722
|
+
return {
|
|
723
|
+
events,
|
|
724
|
+
mode,
|
|
725
|
+
payloadMode,
|
|
726
|
+
direction,
|
|
727
|
+
bytes,
|
|
728
|
+
maxBytes,
|
|
729
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
730
|
+
hasMore: response.headers.get("X-OpenGeni-Has-More") === "true",
|
|
731
|
+
truncatedBy,
|
|
732
|
+
coveredSequence: first === null || last === null ? null : { first, last },
|
|
733
|
+
nextAfter: integerHeader("X-OpenGeni-Next-After"),
|
|
734
|
+
nextBefore: integerHeader("X-OpenGeni-Next-Before"),
|
|
735
|
+
forensicExact: response.headers.get("X-OpenGeni-Forensic-Exact") === "true"
|
|
736
|
+
};
|
|
679
737
|
}
|
|
680
738
|
/** POST a user/control event to the session. Returns the accepted event. */
|
|
681
739
|
async sendEvent(workspaceId, sessionId, event) {
|
|
@@ -710,6 +768,28 @@ var OpenGeniClient = class {
|
|
|
710
768
|
payload
|
|
711
769
|
});
|
|
712
770
|
}
|
|
771
|
+
async listHumanInputRequests(workspaceId, sessionId, options = {}) {
|
|
772
|
+
const result = await this.requestJson(
|
|
773
|
+
"GET",
|
|
774
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests`,
|
|
775
|
+
void 0,
|
|
776
|
+
options.status ? { status: options.status } : void 0
|
|
777
|
+
);
|
|
778
|
+
return result.requests;
|
|
779
|
+
}
|
|
780
|
+
async getHumanInputRequest(workspaceId, sessionId, requestId) {
|
|
781
|
+
return await this.requestJson(
|
|
782
|
+
"GET",
|
|
783
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/human-input-requests/${requestId}`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
async submitHumanInputResponse(workspaceId, sessionId, requestId, response, options = {}) {
|
|
787
|
+
return await this.sendEvent(workspaceId, sessionId, {
|
|
788
|
+
type: "user.humanInputResponse",
|
|
789
|
+
...options.clientEventId ? { clientEventId: options.clientEventId } : {},
|
|
790
|
+
payload: { requestId, response }
|
|
791
|
+
});
|
|
792
|
+
}
|
|
713
793
|
/**
|
|
714
794
|
* Live-stream a session's events with automatic reconnect, resume from the
|
|
715
795
|
* last seen sequence, gap backfill, and duplicate suppression. See
|
|
@@ -818,15 +898,35 @@ var OpenGeniClient = class {
|
|
|
818
898
|
);
|
|
819
899
|
}
|
|
820
900
|
async listWorkspaceControlEvents(workspaceId, options = {}) {
|
|
821
|
-
return await this.
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
901
|
+
return (await this.listWorkspaceControlEventPage(workspaceId, options)).events;
|
|
902
|
+
}
|
|
903
|
+
/** Count/byte-bounded page plus an explicit continuation cursor. */
|
|
904
|
+
async listWorkspaceControlEventPage(workspaceId, options = {}) {
|
|
905
|
+
const response = await this.fetchImpl(
|
|
906
|
+
this.url(`/v1/workspaces/${workspaceId}/control-events`, {
|
|
826
907
|
...options.after !== void 0 ? { after: String(options.after) } : {},
|
|
827
908
|
...options.limit !== void 0 ? { limit: String(options.limit) } : {}
|
|
909
|
+
}),
|
|
910
|
+
{
|
|
911
|
+
method: "GET",
|
|
912
|
+
headers: { ...this.headers(), Accept: "application/json" }
|
|
828
913
|
}
|
|
829
914
|
);
|
|
915
|
+
assertApiContractResponse(response);
|
|
916
|
+
if (!response.ok) {
|
|
917
|
+
throw new OpenGeniApiError(response.status, await safeText(response));
|
|
918
|
+
}
|
|
919
|
+
const events = await response.json();
|
|
920
|
+
const bytesHeader = response.headers.get("X-OpenGeni-Page-Bytes");
|
|
921
|
+
const nextHeader = response.headers.get("X-OpenGeni-Next-After");
|
|
922
|
+
const parsedBytes = bytesHeader === null ? Number.NaN : Number(bytesHeader);
|
|
923
|
+
const parsedNext = nextHeader === null ? null : Number(nextHeader);
|
|
924
|
+
return {
|
|
925
|
+
events,
|
|
926
|
+
bytes: Number.isSafeInteger(parsedBytes) && parsedBytes >= 0 ? parsedBytes : new TextEncoder().encode(JSON.stringify(events)).byteLength,
|
|
927
|
+
truncated: response.headers.get("X-OpenGeni-Page-Truncated") === "true",
|
|
928
|
+
nextAfter: parsedNext !== null && Number.isSafeInteger(parsedNext) && parsedNext >= 0 ? parsedNext : null
|
|
929
|
+
};
|
|
830
930
|
}
|
|
831
931
|
streamWorkspaceControlEvents(workspaceId, options = {}) {
|
|
832
932
|
return streamWorkspaceControlEvents(this.workspaceControlStreamTransport(workspaceId), options);
|
|
@@ -926,19 +1026,23 @@ var OpenGeniClient = class {
|
|
|
926
1026
|
// synchronous API-direct point query; the fs.changed/git.changed/terminal.pty.*
|
|
927
1027
|
// notifications + the PTY output stream arrive on the existing event SSE.
|
|
928
1028
|
/** FileSystem: list a directory tree (feeds the Pierre file tree). */
|
|
929
|
-
async fsList(workspaceId, sessionId, request = {}) {
|
|
1029
|
+
async fsList(workspaceId, sessionId, request = {}, options = {}) {
|
|
930
1030
|
return await this.requestJson(
|
|
931
1031
|
"POST",
|
|
932
1032
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list`,
|
|
933
|
-
request
|
|
1033
|
+
request,
|
|
1034
|
+
{},
|
|
1035
|
+
options
|
|
934
1036
|
);
|
|
935
1037
|
}
|
|
936
1038
|
/** FileSystem: read a file (text or base64; binary-safe, size-capped). */
|
|
937
|
-
async fsRead(workspaceId, sessionId, request) {
|
|
1039
|
+
async fsRead(workspaceId, sessionId, request, options = {}) {
|
|
938
1040
|
return await this.requestJson(
|
|
939
1041
|
"POST",
|
|
940
1042
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/read`,
|
|
941
|
-
request
|
|
1043
|
+
request,
|
|
1044
|
+
{},
|
|
1045
|
+
options
|
|
942
1046
|
);
|
|
943
1047
|
}
|
|
944
1048
|
/** FileSystem: write a file (last-writer-wins; emits fs.changed). */
|
|
@@ -974,19 +1078,23 @@ var OpenGeniClient = class {
|
|
|
974
1078
|
);
|
|
975
1079
|
}
|
|
976
1080
|
/** Git: working-tree/index status (the Pierre file-status feed). */
|
|
977
|
-
async gitStatus(workspaceId, sessionId, request = {}) {
|
|
1081
|
+
async gitStatus(workspaceId, sessionId, request = {}, options = {}) {
|
|
978
1082
|
return await this.requestJson(
|
|
979
1083
|
"POST",
|
|
980
1084
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/status`,
|
|
981
|
-
request
|
|
1085
|
+
request,
|
|
1086
|
+
{},
|
|
1087
|
+
options
|
|
982
1088
|
);
|
|
983
1089
|
}
|
|
984
1090
|
/** Git: structured diff hunks (the Pierre diff feed). */
|
|
985
|
-
async gitDiff(workspaceId, sessionId, request = {}) {
|
|
1091
|
+
async gitDiff(workspaceId, sessionId, request = {}, options = {}) {
|
|
986
1092
|
return await this.requestJson(
|
|
987
1093
|
"POST",
|
|
988
1094
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/diff`,
|
|
989
|
-
request
|
|
1095
|
+
request,
|
|
1096
|
+
{},
|
|
1097
|
+
options
|
|
990
1098
|
);
|
|
991
1099
|
}
|
|
992
1100
|
/** Git: commit log. */
|
|
@@ -1009,23 +1117,27 @@ var OpenGeniClient = class {
|
|
|
1009
1117
|
* (tree + per-repo diff + file after-image refs), served from durable storage
|
|
1010
1118
|
* WITHOUT warming a machine — the workbench cold-paint source. Returns
|
|
1011
1119
|
* `{available:false}` when no capture exists yet (fall back to the live path). */
|
|
1012
|
-
async getWorkspaceCapture(workspaceId, sessionId) {
|
|
1120
|
+
async getWorkspaceCapture(workspaceId, sessionId, options = {}) {
|
|
1013
1121
|
return await this.requestJson(
|
|
1014
1122
|
"GET",
|
|
1015
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture
|
|
1123
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture`,
|
|
1124
|
+
void 0,
|
|
1125
|
+
{},
|
|
1126
|
+
options
|
|
1016
1127
|
);
|
|
1017
1128
|
}
|
|
1018
1129
|
/** Workspace capture: a single file's after-image from the capture (revision
|
|
1019
1130
|
* pins a specific one; omitted → latest). Content is inline for small files,
|
|
1020
1131
|
* else a short-TTL signed URL; a tooLarge file returns metadata only. */
|
|
1021
|
-
async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision) {
|
|
1132
|
+
async getWorkspaceCaptureFile(workspaceId, sessionId, path, revision, options = {}) {
|
|
1022
1133
|
const query = { path };
|
|
1023
1134
|
if (revision !== void 0) query.revision = String(revision);
|
|
1024
1135
|
return await this.requestJson(
|
|
1025
1136
|
"GET",
|
|
1026
1137
|
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/workspace/capture/file`,
|
|
1027
1138
|
void 0,
|
|
1028
|
-
query
|
|
1139
|
+
query,
|
|
1140
|
+
options
|
|
1029
1141
|
);
|
|
1030
1142
|
}
|
|
1031
1143
|
/** Terminal: run a bounded command, returning buffered stdout/stderr inline. */
|
|
@@ -1080,10 +1192,13 @@ var OpenGeniClient = class {
|
|
|
1080
1192
|
* liveness the client polls on while `cold`/`warming`. The desktop URL/token
|
|
1081
1193
|
* are minted in-process only when the box is warm AND the principal has
|
|
1082
1194
|
* acknowledged the un-redacted plane. */
|
|
1083
|
-
async getStreamCapabilities(workspaceId, sessionId) {
|
|
1195
|
+
async getStreamCapabilities(workspaceId, sessionId, options = {}) {
|
|
1084
1196
|
return await this.requestJson(
|
|
1085
1197
|
"GET",
|
|
1086
|
-
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities
|
|
1198
|
+
`/v1/workspaces/${workspaceId}/sessions/${sessionId}/stream-capabilities`,
|
|
1199
|
+
void 0,
|
|
1200
|
+
{},
|
|
1201
|
+
options
|
|
1087
1202
|
);
|
|
1088
1203
|
}
|
|
1089
1204
|
/** Record the calling principal's acknowledgment of the un-redacted desktop
|
|
@@ -1727,14 +1842,13 @@ var OpenGeniClient = class {
|
|
|
1727
1842
|
return logoAssetPath ? `${this.baseUrl}/v1/${logoAssetPath}` : null;
|
|
1728
1843
|
}
|
|
1729
1844
|
// --- GitHub ----------------------------------------------------------------------------------
|
|
1730
|
-
/** GitHub App configuration status
|
|
1845
|
+
/** GitHub App configuration status; install/link URLs are null while new binding is disabled. */
|
|
1731
1846
|
async getGitHubApp(workspaceId) {
|
|
1732
1847
|
return await this.requestJson("GET", `/v1/workspaces/${workspaceId}/github/app`);
|
|
1733
1848
|
}
|
|
1734
1849
|
/**
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
* `getGitHubApp().installUrl` or a github_connect_link tool.
|
|
1850
|
+
* Compatibility URL for previously issued state. New installation binding is
|
|
1851
|
+
* disabled, so the endpoint validates state and terminates with HTTP 410.
|
|
1738
1852
|
*/
|
|
1739
1853
|
githubConnectUrl(workspaceId, state) {
|
|
1740
1854
|
return this.url(`/v1/workspaces/${workspaceId}/github/connect`, { state });
|
|
@@ -1752,6 +1866,13 @@ var OpenGeniClient = class {
|
|
|
1752
1866
|
`/v1/workspaces/${workspaceId}/github/repositories/sync`
|
|
1753
1867
|
);
|
|
1754
1868
|
}
|
|
1869
|
+
/** Remove one workspace binding without uninstalling the GitHub App itself. */
|
|
1870
|
+
async unlinkGitHubInstallation(workspaceId, installationId) {
|
|
1871
|
+
await this.requestVoid(
|
|
1872
|
+
"DELETE",
|
|
1873
|
+
`/v1/workspaces/${workspaceId}/github/installations/${installationId}`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1755
1876
|
/** Build a GitHub App manifest + the GitHub URL to submit it to. */
|
|
1756
1877
|
async createGitHubAppManifest(workspaceId, request = {}) {
|
|
1757
1878
|
return await this.requestJson(
|
|
@@ -1915,7 +2036,7 @@ var OpenGeniClient = class {
|
|
|
1915
2036
|
{ target }
|
|
1916
2037
|
);
|
|
1917
2038
|
}
|
|
1918
|
-
async requestJson(method, path, body, query = {}) {
|
|
2039
|
+
async requestJson(method, path, body, query = {}, options = {}) {
|
|
1919
2040
|
const response = await this.fetchImpl(this.url(path, query), {
|
|
1920
2041
|
method,
|
|
1921
2042
|
headers: {
|
|
@@ -1923,7 +2044,8 @@ var OpenGeniClient = class {
|
|
|
1923
2044
|
Accept: "application/json",
|
|
1924
2045
|
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
1925
2046
|
},
|
|
1926
|
-
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2047
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
2048
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1927
2049
|
});
|
|
1928
2050
|
assertApiContractResponse(response);
|
|
1929
2051
|
if (!response.ok) {
|
|
@@ -2144,7 +2266,221 @@ function ttydInputFrame(data) {
|
|
|
2144
2266
|
function ttydResizeFrame(columns, rows) {
|
|
2145
2267
|
return TtydClientCommand.RESIZE + JSON.stringify({ columns, rows });
|
|
2146
2268
|
}
|
|
2269
|
+
|
|
2270
|
+
// src/transcription.ts
|
|
2271
|
+
var DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY = {
|
|
2272
|
+
enabled: false,
|
|
2273
|
+
acceptanceId: null,
|
|
2274
|
+
primary: null,
|
|
2275
|
+
language: null,
|
|
2276
|
+
autoDetectLanguage: false,
|
|
2277
|
+
diarization: { enabled: false, maxSpeakers: null },
|
|
2278
|
+
retention: { mode: "none", maxDays: null },
|
|
2279
|
+
privacy: { allowProviderLogging: false, allowProviderTraining: false },
|
|
2280
|
+
fallback: { mode: "disabled", targets: [] },
|
|
2281
|
+
cost: { currency: "USD", maxPerHour: null, maxPerMonth: null }
|
|
2282
|
+
};
|
|
2283
|
+
function resolveWorkspaceTranscriptionPolicy(settings) {
|
|
2284
|
+
if (!isRecord(settings)) return cloneDefaultPolicy();
|
|
2285
|
+
const candidate = settings.transcription;
|
|
2286
|
+
if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
|
|
2287
|
+
return {
|
|
2288
|
+
...candidate,
|
|
2289
|
+
primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
|
|
2290
|
+
language: candidate.language?.trim() ?? null,
|
|
2291
|
+
diarization: { ...candidate.diarization },
|
|
2292
|
+
retention: { ...candidate.retention },
|
|
2293
|
+
privacy: { ...candidate.privacy },
|
|
2294
|
+
fallback: {
|
|
2295
|
+
mode: candidate.fallback.mode,
|
|
2296
|
+
targets: candidate.fallback.targets.map(normalizeTarget)
|
|
2297
|
+
},
|
|
2298
|
+
cost: { ...candidate.cost }
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
function authorizeTranscriptionAdapter(policy, descriptor, selection = { kind: "primary" }) {
|
|
2302
|
+
if (!isWorkspaceTranscriptionPolicy(policy)) {
|
|
2303
|
+
return { authorized: false, reason: "unaccepted" };
|
|
2304
|
+
}
|
|
2305
|
+
if (!policy.enabled) return { authorized: false, reason: "disabled" };
|
|
2306
|
+
if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
|
|
2307
|
+
let target;
|
|
2308
|
+
if (selection.kind === "primary") {
|
|
2309
|
+
target = policy.primary;
|
|
2310
|
+
} else {
|
|
2311
|
+
if (policy.fallback.mode !== "explicit") {
|
|
2312
|
+
return { authorized: false, reason: "fallback_disabled" };
|
|
2313
|
+
}
|
|
2314
|
+
target = policy.fallback.targets[selection.index];
|
|
2315
|
+
if (!target) return { authorized: false, reason: "fallback_unaccepted" };
|
|
2316
|
+
}
|
|
2317
|
+
if (!target) return { authorized: false, reason: "target_missing" };
|
|
2318
|
+
const acceptedTarget = normalizeTarget(target);
|
|
2319
|
+
if (acceptedTarget.provider !== descriptor.provider) {
|
|
2320
|
+
return { authorized: false, reason: "provider_mismatch" };
|
|
2321
|
+
}
|
|
2322
|
+
if (acceptedTarget.model !== descriptor.model) {
|
|
2323
|
+
return { authorized: false, reason: "model_mismatch" };
|
|
2324
|
+
}
|
|
2325
|
+
if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
|
|
2326
|
+
return { authorized: false, reason: "credential_mode_mismatch" };
|
|
2327
|
+
}
|
|
2328
|
+
if (acceptedTarget.region !== descriptor.region) {
|
|
2329
|
+
return { authorized: false, reason: "region_mismatch" };
|
|
2330
|
+
}
|
|
2331
|
+
return {
|
|
2332
|
+
authorized: true,
|
|
2333
|
+
acceptanceId: policy.acceptanceId,
|
|
2334
|
+
target: acceptedTarget,
|
|
2335
|
+
selection
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function createTranscriptionSessionRequest(input) {
|
|
2339
|
+
const sequenceFloor = input.sequenceFloor ?? 0;
|
|
2340
|
+
if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
|
|
2341
|
+
const authorization = authorizeTranscriptionAdapter(
|
|
2342
|
+
input.policy,
|
|
2343
|
+
input.adapter.descriptor,
|
|
2344
|
+
input.selection
|
|
2345
|
+
);
|
|
2346
|
+
if (!authorization.authorized) return null;
|
|
2347
|
+
return {
|
|
2348
|
+
localSessionId: input.localSessionId,
|
|
2349
|
+
policyAcceptanceId: authorization.acceptanceId,
|
|
2350
|
+
selection: authorization.selection,
|
|
2351
|
+
target: { ...authorization.target },
|
|
2352
|
+
language: input.policy.language?.trim() ?? null,
|
|
2353
|
+
autoDetectLanguage: input.policy.autoDetectLanguage,
|
|
2354
|
+
diarization: { ...input.policy.diarization },
|
|
2355
|
+
retention: { ...input.policy.retention },
|
|
2356
|
+
privacy: { ...input.policy.privacy },
|
|
2357
|
+
cost: { ...input.policy.cost },
|
|
2358
|
+
sequenceFloor
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
function cloneDefaultPolicy() {
|
|
2362
|
+
return {
|
|
2363
|
+
...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
2364
|
+
diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
|
|
2365
|
+
retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
|
|
2366
|
+
privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
|
|
2367
|
+
fallback: { mode: "disabled", targets: [] },
|
|
2368
|
+
cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost }
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function isWorkspaceTranscriptionPolicy(value) {
|
|
2372
|
+
if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
|
|
2373
|
+
if (!hasOnlyKeys(value, [
|
|
2374
|
+
"enabled",
|
|
2375
|
+
"acceptanceId",
|
|
2376
|
+
"primary",
|
|
2377
|
+
"language",
|
|
2378
|
+
"autoDetectLanguage",
|
|
2379
|
+
"diarization",
|
|
2380
|
+
"retention",
|
|
2381
|
+
"privacy",
|
|
2382
|
+
"fallback",
|
|
2383
|
+
"cost"
|
|
2384
|
+
])) {
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
|
|
2388
|
+
if (!(value.primary === null || isTarget(value.primary))) return false;
|
|
2389
|
+
if (!(value.language === null || isBoundedString(value.language, 64))) return false;
|
|
2390
|
+
if (typeof value.autoDetectLanguage !== "boolean") return false;
|
|
2391
|
+
if (!isRecord(value.diarization) || !hasOnlyKeys(value.diarization, ["enabled", "maxSpeakers"]) || typeof value.diarization.enabled !== "boolean" || !(value.diarization.maxSpeakers === null || isBoundedInteger(value.diarization.maxSpeakers, 100) && value.diarization.maxSpeakers >= 2)) {
|
|
2392
|
+
return false;
|
|
2393
|
+
}
|
|
2394
|
+
if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
|
|
2395
|
+
if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
|
|
2396
|
+
return false;
|
|
2397
|
+
}
|
|
2398
|
+
if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
|
|
2399
|
+
if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
|
|
2400
|
+
return false;
|
|
2401
|
+
}
|
|
2402
|
+
if (!isRecord(value.privacy) || !hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) || typeof value.privacy.allowProviderLogging !== "boolean" || typeof value.privacy.allowProviderTraining !== "boolean") {
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
|
|
2406
|
+
return false;
|
|
2407
|
+
}
|
|
2408
|
+
if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
|
|
2409
|
+
if (!Array.isArray(value.fallback.targets) || value.fallback.targets.length > 8 || !value.fallback.targets.every(isTarget)) {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
|
|
2413
|
+
if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
|
|
2414
|
+
if (!isRecord(value.cost) || !hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) || value.cost.currency !== "USD") {
|
|
2415
|
+
return false;
|
|
2416
|
+
}
|
|
2417
|
+
if (!isNullableBoundedNumber(value.cost.maxPerHour, 1e4)) return false;
|
|
2418
|
+
if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1e6)) return false;
|
|
2419
|
+
if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
|
|
2420
|
+
if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
|
|
2421
|
+
if (value.autoDetectLanguage && value.language !== null) return false;
|
|
2422
|
+
const targets = [value.primary, ...value.fallback.targets].filter(
|
|
2423
|
+
(target) => target !== null
|
|
2424
|
+
);
|
|
2425
|
+
if (new Set(targets.map(targetKey)).size !== targets.length) return false;
|
|
2426
|
+
return true;
|
|
2427
|
+
}
|
|
2428
|
+
function targetKey(target) {
|
|
2429
|
+
return [
|
|
2430
|
+
target.provider.trim(),
|
|
2431
|
+
target.model?.trim() ?? "",
|
|
2432
|
+
target.credentialMode,
|
|
2433
|
+
target.credentialConnectionId ?? "",
|
|
2434
|
+
target.region?.trim() ?? ""
|
|
2435
|
+
].join("\0");
|
|
2436
|
+
}
|
|
2437
|
+
function isTarget(value) {
|
|
2438
|
+
if (!isRecord(value)) return false;
|
|
2439
|
+
if (!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])) {
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
if (!isBoundedString(value.provider, 128)) return false;
|
|
2443
|
+
if (!(value.model === null || isBoundedString(value.model, 256))) return false;
|
|
2444
|
+
if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
|
|
2445
|
+
if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
|
|
2446
|
+
if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
if (!(value.region === null || isBoundedString(value.region, 128))) return false;
|
|
2450
|
+
if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
|
|
2451
|
+
if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
|
|
2452
|
+
return true;
|
|
2453
|
+
}
|
|
2454
|
+
function normalizeTarget(target) {
|
|
2455
|
+
return {
|
|
2456
|
+
provider: target.provider.trim(),
|
|
2457
|
+
model: target.model?.trim() ?? null,
|
|
2458
|
+
credentialMode: target.credentialMode,
|
|
2459
|
+
credentialConnectionId: target.credentialConnectionId,
|
|
2460
|
+
region: target.region?.trim() ?? null
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
function isRecord(value) {
|
|
2464
|
+
return typeof value === "object" && value !== null;
|
|
2465
|
+
}
|
|
2466
|
+
function hasOnlyKeys(value, keys) {
|
|
2467
|
+
const accepted = new Set(keys);
|
|
2468
|
+
return Object.keys(value).every((key) => accepted.has(key));
|
|
2469
|
+
}
|
|
2470
|
+
function isBoundedString(value, maximum) {
|
|
2471
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
2472
|
+
}
|
|
2473
|
+
function isUuid(value) {
|
|
2474
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
2475
|
+
}
|
|
2476
|
+
function isBoundedInteger(value, maximum) {
|
|
2477
|
+
return Number.isInteger(value) && value >= 0 && value <= maximum;
|
|
2478
|
+
}
|
|
2479
|
+
function isNullableBoundedNumber(value, maximum) {
|
|
2480
|
+
return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum;
|
|
2481
|
+
}
|
|
2147
2482
|
export {
|
|
2483
|
+
DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
2148
2484
|
KNOWN_PERMISSIONS,
|
|
2149
2485
|
KNOWN_USAGE_EVENT_TYPES,
|
|
2150
2486
|
OPENGENI_API_CONTRACT_HEADER,
|
|
@@ -2158,12 +2494,15 @@ export {
|
|
|
2158
2494
|
TtydClientCommand,
|
|
2159
2495
|
TtydServerCommand,
|
|
2160
2496
|
applyUrlRotation,
|
|
2497
|
+
authorizeTranscriptionAdapter,
|
|
2498
|
+
createTranscriptionSessionRequest,
|
|
2161
2499
|
desktopSocketUrl,
|
|
2162
2500
|
formatSseEvent,
|
|
2163
2501
|
isRetryableStreamError,
|
|
2164
2502
|
nextDesktopState,
|
|
2165
2503
|
parseSseStream,
|
|
2166
2504
|
proxySessionEventStream,
|
|
2505
|
+
resolveWorkspaceTranscriptionPolicy,
|
|
2167
2506
|
resumeSequenceFromRequest,
|
|
2168
2507
|
sessionEventsToSseResponse,
|
|
2169
2508
|
sessionEventsToSseStream,
|