@opengeni/sdk 0.46.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +94 -2
  2. package/dist/artifact-client.d.ts +20 -2
  3. package/dist/artifacts.d.ts +4 -0
  4. package/dist/artifacts.js +11 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/chunk-FHI4DFIG.js +128 -0
  7. package/dist/chunk-FHI4DFIG.js.map +1 -0
  8. package/dist/chunk-FRJFNDIR.js +218 -0
  9. package/dist/chunk-FRJFNDIR.js.map +1 -0
  10. package/dist/{chunk-QKV5OCLJ.js → chunk-ILQZR5N6.js} +1380 -595
  11. package/dist/chunk-ILQZR5N6.js.map +1 -0
  12. package/dist/chunk-MZWTWOX6.js +661 -0
  13. package/dist/chunk-MZWTWOX6.js.map +1 -0
  14. package/dist/chunk-VWE2QIVO.js +1162 -0
  15. package/dist/chunk-VWE2QIVO.js.map +1 -0
  16. package/dist/chunk-VYCTL62C.js +230 -0
  17. package/dist/chunk-VYCTL62C.js.map +1 -0
  18. package/dist/client.d.ts +176 -20
  19. package/dist/codex-realtime-controller.d.ts +6 -6
  20. package/dist/codex-realtime-lifecycle.d.ts +1 -1
  21. package/dist/codex-realtime-v3.d.ts +3 -3
  22. package/dist/codex-realtime.d.ts +1 -1
  23. package/dist/company-profile.d.ts +100 -0
  24. package/dist/core.d.ts +5 -5
  25. package/dist/core.js +9 -4
  26. package/dist/desktop.d.ts +1 -1
  27. package/dist/editable-artifact-resources.d.ts +96 -0
  28. package/dist/editable-artifacts/browser-session.d.ts +35 -0
  29. package/dist/editable-artifacts/controller.d.ts +60 -0
  30. package/dist/editable-artifacts/errors.d.ts +23 -0
  31. package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
  32. package/dist/editable-artifacts/index.d.ts +19 -0
  33. package/dist/editable-artifacts/pool.d.ts +15 -0
  34. package/dist/editable-artifacts/session.d.ts +96 -0
  35. package/dist/editable-artifacts/storage.d.ts +72 -0
  36. package/dist/editable-artifacts/types.d.ts +416 -0
  37. package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
  38. package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
  39. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
  40. package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
  41. package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
  42. package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
  43. package/dist/editable-artifacts-worker.d.ts +8 -0
  44. package/dist/editable-artifacts-worker.js +1708 -0
  45. package/dist/editable-artifacts-worker.js.map +1 -0
  46. package/dist/editable-artifacts.d.ts +6 -0
  47. package/dist/editable-artifacts.js +5954 -0
  48. package/dist/editable-artifacts.js.map +1 -0
  49. package/dist/gateway-realtime-transport.d.ts +1 -1
  50. package/dist/index.d.ts +41 -35
  51. package/dist/index.js +104 -68
  52. package/dist/index.js.map +1 -1
  53. package/dist/interaction.d.ts +885 -0
  54. package/dist/interaction.js +55 -0
  55. package/dist/interaction.js.map +1 -0
  56. package/dist/memory-slack-client.d.ts +13 -0
  57. package/dist/memory-slack-delivery.d.ts +99 -0
  58. package/dist/memory-slack.d.ts +4 -0
  59. package/dist/memory-slack.js +44 -0
  60. package/dist/memory-slack.js.map +1 -0
  61. package/dist/proxy.d.ts +3 -3
  62. package/dist/realtime.d.ts +12 -12
  63. package/dist/retained-artifacts.d.ts +12 -0
  64. package/dist/stream.d.ts +16 -1
  65. package/dist/terminal.d.ts +1 -1
  66. package/dist/transcription.d.ts +2 -2
  67. package/dist/types.d.ts +1203 -23
  68. package/dist/workspace-control-stream.d.ts +2 -2
  69. package/dist/workspace-state.d.ts +1 -1
  70. package/package.json +29 -3
  71. package/src/artifact-client.ts +129 -0
  72. package/src/artifacts.ts +17 -0
  73. package/src/client.ts +1801 -206
  74. package/src/company-profile.ts +92 -0
  75. package/src/editable-artifact-resources.ts +119 -0
  76. package/src/editable-artifacts/browser-session.ts +176 -0
  77. package/src/editable-artifacts/controller.ts +2632 -0
  78. package/src/editable-artifacts/errors.ts +52 -0
  79. package/src/editable-artifacts/http-live-transport.ts +1449 -0
  80. package/src/editable-artifacts/index.ts +175 -0
  81. package/src/editable-artifacts/pool.ts +81 -0
  82. package/src/editable-artifacts/session.ts +778 -0
  83. package/src/editable-artifacts/storage.ts +1718 -0
  84. package/src/editable-artifacts/types.ts +499 -0
  85. package/src/editable-artifacts/worker/browser-client.ts +1167 -0
  86. package/src/editable-artifacts/worker/browser-entry.ts +57 -0
  87. package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
  88. package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
  89. package/src/editable-artifacts/worker/runtime.ts +1204 -0
  90. package/src/editable-artifacts/worker/wire-codec.ts +852 -0
  91. package/src/editable-artifacts-worker.ts +8 -0
  92. package/src/editable-artifacts.ts +6 -0
  93. package/src/index.ts +194 -0
  94. package/src/interaction.ts +1830 -0
  95. package/src/memory-slack-client.ts +71 -0
  96. package/src/memory-slack-delivery.ts +128 -0
  97. package/src/memory-slack.ts +19 -0
  98. package/src/retained-artifacts.ts +326 -0
  99. package/src/stream.ts +102 -4
  100. package/src/transcription.ts +2 -2
  101. package/src/types.ts +1430 -33
  102. package/src/workspace-control-stream.ts +15 -5
  103. package/dist/chunk-QKV5OCLJ.js.map +0 -1
package/src/client.ts CHANGED
@@ -12,6 +12,48 @@ import {
12
12
  streamWorkspaceControlEvents,
13
13
  type WorkspaceControlStreamTransport,
14
14
  } from "./workspace-control-stream";
15
+ import {
16
+ OpenGeniInteractionClient,
17
+ type AttachedBrowserDevice,
18
+ type AttachedBrowserDeviceListOptions,
19
+ type AttachedBrowserDeviceListResponse,
20
+ type BrowserActionReceipt,
21
+ type BrowserActionRequest,
22
+ type BrowserDiagnosticBatch,
23
+ type BrowserDiagnosticsOptions,
24
+ type BrowserIdentity,
25
+ type BrowserIdentityListOptions,
26
+ type BrowserIdentityListResponse,
27
+ type BrowserIdentityMutationResponse,
28
+ type BrowserObservation,
29
+ type BrowserOpenTargetRequest,
30
+ type BrowserSession,
31
+ type BrowserSessionAttachment,
32
+ type BrowserSessionAttachmentRequest,
33
+ type BrowserSessionHeartbeatResponse,
34
+ type BrowserSessionLifecycleRequest,
35
+ type BrowserSessionListResponse,
36
+ type BrowserSessionMutationResponse,
37
+ type BrowserTarget,
38
+ type BrowserTargetListResponse,
39
+ type BrowserRevisionListResponse,
40
+ type ComputerActionReceipt,
41
+ type ComputerActionRequest,
42
+ type ComputerObservation,
43
+ type ComputerSession,
44
+ type ComputerSessionAttachment,
45
+ type ComputerSessionAttachmentRequest,
46
+ type ComputerSessionHeartbeatResponse,
47
+ type ComputerSessionLifecycleRequest,
48
+ type ComputerSessionListResponse,
49
+ type ComputerSessionMutationResponse,
50
+ type ComputerTargetListResponse,
51
+ type CreateBrowserIdentityRequest,
52
+ type CreateBrowserSessionRequest,
53
+ type CreateComputerSessionRequest,
54
+ type PublishBrowserRevisionRequest,
55
+ type PublishBrowserRevisionResponse,
56
+ } from "./interaction";
15
57
  import type {
16
58
  AccessContext,
17
59
  ActivateCodexRealtimeConnectionRequest,
@@ -41,6 +83,29 @@ import type {
41
83
  CapabilityCatalogItem,
42
84
  CapabilityCatalogResponse,
43
85
  CapabilityInstallation,
86
+ ApiIntegrationPreview,
87
+ ApiIntegrationOAuthStartRequest,
88
+ ApiIntegrationUninstallPreview,
89
+ InstallApiIntegrationRequest,
90
+ InstalledApiIntegration,
91
+ IntegrationFeatureMutationResult,
92
+ IntegrationFeatureRemovalResult,
93
+ IntegrationInstanceFeaturesResponse,
94
+ ListApiIntegrationPresetsResponse,
95
+ ListApiIntegrationsResponse,
96
+ MutateIntegrationFeatureRequest,
97
+ PreviewApiIntegrationRequest,
98
+ UninstallApiIntegrationRequest,
99
+ UninstallApiIntegrationResult,
100
+ UpsertIntegrationFeatureRequest,
101
+ PreviewPluginRequest,
102
+ PluginPreview,
103
+ InstallPluginRequest,
104
+ InstalledPlugin,
105
+ ListInstalledPluginsResponse,
106
+ PluginUninstallPreview,
107
+ UninstallPluginRequest,
108
+ UninstallPluginResult,
44
109
  AddDocumentRequest,
45
110
  CreateKnowledgeDropRequest,
46
111
  MoveDocumentRequest,
@@ -54,6 +119,8 @@ import type {
54
119
  CreateApiKeyRequest,
55
120
  CreateApiKeyResponse,
56
121
  CreateCapabilityCatalogItemRequest,
122
+ InstallSkillRequest,
123
+ InstalledSkill,
57
124
  CreateCheckoutRequest,
58
125
  CreateCheckoutResponse,
59
126
  OpenGeniSlackBotInstallRequest,
@@ -91,7 +158,9 @@ import type {
91
158
  GetPackResponse,
92
159
  GitHubAppInfo,
93
160
  GitHubRepositoriesResponse,
161
+ GoogleDriveBrowseResponse,
94
162
  GoogleDriveDisconnectRequest,
163
+ SaveGoogleDriveIntegrationSourceRequest,
95
164
  GoogleDriveLifecycleActionRequest,
96
165
  KnowledgeMemory,
97
166
  KnowledgeMemorySearchRequest,
@@ -101,23 +170,48 @@ import type {
101
170
  MachinesResponse,
102
171
  MetricSample,
103
172
  MachineMetricsSeriesResponse,
173
+ RemoveEnrollmentRequest,
174
+ RemoveEnrollmentResponse,
104
175
  // Bring-your-own-compute: the user-authenticated active-sandbox swap (M7).
105
176
  SwapActiveSandboxRequest,
106
177
  SwapActiveSandboxResponse,
107
178
  ListWorkspaceMembersResponse,
179
+ InstallPackRequest,
180
+ PackInstallationPreview,
181
+ ListSlackUserLinkAccessRequestsResponse,
182
+ PrepareSlackUserLinkAccessRequest,
183
+ SlackUserLinkAccessMutationRequest,
184
+ SlackUserLinkAccessRequest,
185
+ ApproveSlackUserLinkAccessRequest,
108
186
  PackInstallation,
187
+ PackUninstallPreview,
109
188
  LatencyMode,
110
189
  ReasoningEffort,
190
+ RetainedScreenshotDownload,
191
+ RetainedScreenshotDownloadOptions,
192
+ RetainedArtifactDownload,
193
+ RetainedArtifactDownloadOptions,
194
+ RetainedArtifactReference,
111
195
  RetainedArtifactContent,
112
196
  RetainedArtifactContentOptions,
113
197
  RetainedArtifactMetadata,
198
+ UpdateVideoGenerationPolicyRequest,
199
+ VideoArtifactPlaybackSource,
200
+ VideoGenerationOperationSummary,
201
+ VideoGenerationPolicy,
202
+ WorkspaceVideoGenerationSettings,
114
203
  RegisterCapabilityPackRequest,
115
204
  ResourceRef,
205
+ PreviewPackInstallationRequest,
206
+ PreviewSkillImportRequest,
116
207
  ScheduledTask,
117
208
  ScheduledTaskRun,
118
209
  Session,
119
210
  SessionListResponse,
211
+ AgentTopologyPageResponse,
120
212
  UpdateSessionPinRequest,
213
+ UninstallPackRequest,
214
+ UninstallPackResult,
121
215
  SessionEvent,
122
216
  SessionEventCompactResult,
123
217
  SessionEventCompactResultOptions,
@@ -131,6 +225,7 @@ import type {
131
225
  SyncSessionRealtimeLedgerResponse,
132
226
  RenewSessionRealtimeRequest,
133
227
  SessionMcpCredentialUpdateInput,
228
+ SubmittedTimelineAnnotation,
134
229
  UpdateSessionMcpApprovalPolicyRequest,
135
230
  UpdateSessionMcpApprovalPolicyResponse,
136
231
  SessionQueueSnapshot,
@@ -159,6 +254,8 @@ import type {
159
254
  // Channel-A structured services (P4.4).
160
255
  FsListRequest,
161
256
  FsListResponse,
257
+ FsListBatchRequest,
258
+ FsListBatchResponse,
162
259
  FsReadRequest,
163
260
  FsReadResponse,
164
261
  FsWriteRequest,
@@ -173,6 +270,8 @@ import type {
173
270
  GitStatusResponse,
174
271
  GitDiffRequest,
175
272
  GitDiffResponse,
273
+ GitReadBatchRequest,
274
+ GitReadBatchResponse,
176
275
  GitLogRequest,
177
276
  GitLogResponse,
178
277
  GitShowRequest,
@@ -206,6 +305,7 @@ import type {
206
305
  SetWorkspaceDefaultRigRequest,
207
306
  UploadFileInput,
208
307
  VariableSet,
308
+ VariableSetSecret,
209
309
  VariableSetVariableMetadata,
210
310
  Rig,
211
311
  RigVersion,
@@ -217,12 +317,20 @@ import type {
217
317
  WorkspaceRegisteredPack,
218
318
  Workspace,
219
319
  ListConnectionsResponse,
320
+ ListSlackInstallationBindingsResponse,
321
+ SlackInstallationBinding,
220
322
  ConnectionResponse,
221
323
  OAuthStartRequest,
222
324
  OAuthStartResponse,
223
325
  SocialConnection,
224
326
  SocialOAuthStartRequest,
327
+ SkillImportPreview,
328
+ SkillUninstallPreview,
329
+ UninstallSkillRequest,
330
+ UninstallSkillResult,
225
331
  } from "./types";
332
+ import { GENERATED_VIDEO_MAX_BYTES } from "./types";
333
+ import { parseRetainedGeneratedImageReference } from "./retained-artifacts";
226
334
  import type {
227
335
  ActivateWorkspaceInstructionPolicyRequest,
228
336
  CreateWorkspaceInstructionPolicyDraftRequest,
@@ -239,6 +347,17 @@ import type {
239
347
  WorkspaceInstructionPolicyOnboardingProposalListResponse,
240
348
  WorkspaceInstructionPolicyRevision,
241
349
  } from "./workspace-instruction-policies";
350
+ import type {
351
+ ActivateCompanyProfileRevisionRequest,
352
+ CompanyProfileDiffRequest,
353
+ CompanyProfileDiffResponse,
354
+ CompanyProfileListOptions,
355
+ CompanyProfileListResponse,
356
+ CompanyProfileMutationResponse,
357
+ CompanyProfileRevision,
358
+ RollbackCompanyProfileRequest,
359
+ UpdateCompanyProfileRequest,
360
+ } from "./company-profile";
242
361
  import type {
243
362
  WorkspaceStateExportResponse,
244
363
  WorkspaceStateGetOptions,
@@ -264,6 +383,7 @@ import {
264
383
  OPENGENI_API_CONTRACT_HEADER,
265
384
  OPENGENI_API_CONTRACT_REVISION,
266
385
  OPENGENI_CORRELATION_HEADER,
386
+ COMPUTER_SCREENSHOT_MAX_BYTES,
267
387
  RETAINED_OUTPUT_MAX_PAGE_BYTES,
268
388
  } from "./types";
269
389
 
@@ -305,6 +425,7 @@ export type OpenGeniRequestOptions = {
305
425
 
306
426
  export type SendMessageInput = {
307
427
  text: string;
428
+ annotations?: SubmittedTimelineAnnotation[];
308
429
  /** System instructions scoped to this exact turn; never visible timeline text. */
309
430
  turnInstructions?: string;
310
431
  resources?: ResourceRef[];
@@ -323,6 +444,10 @@ export type SteerMessageResult = {
323
444
  accepted: SessionEvent;
324
445
  /** The exact turn created for this message in the same server transaction. */
325
446
  turn: SessionTurn;
447
+ /** Number of live attempts durably asked to stop by this atomic Steer. */
448
+ interruptionCount?: number;
449
+ /** True when this response came from the command's immutable idempotency receipt. */
450
+ replay?: boolean;
326
451
  };
327
452
 
328
453
  export type TranscribeAudioInput = {
@@ -363,12 +488,17 @@ export class OpenGeniClient {
363
488
  private readonly baseUrl: string;
364
489
  private readonly options: OpenGeniClientOptions;
365
490
  private readonly fetchImpl: FetchLike;
491
+ private readonly readInFlight = new Map<string, Promise<unknown>>();
492
+ private readonly readTrailing = new Map<string, Promise<unknown>>();
493
+ /** Resource-oriented Browser/Computer facade over this exact authenticated client. */
494
+ readonly interaction: OpenGeniInteractionClient;
366
495
 
367
496
  constructor(options: OpenGeniClientOptions) {
368
497
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
369
498
  this.options = options;
370
499
  // Bind lazily so variable sets that polyfill fetch after module load work.
371
500
  this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
501
+ this.interaction = new OpenGeniInteractionClient(this);
372
502
  }
373
503
 
374
504
  // --- Session lifecycle ---------------------------------------------------
@@ -385,8 +515,12 @@ export class OpenGeniClient {
385
515
  input.audio instanceof File
386
516
  ? input.audio
387
517
  : input.audio instanceof Uint8Array
388
- ? new File([Uint8Array.from(input.audio)], filename, { type: input.mimeType })
389
- : new File([input.audio], filename, { type: input.mimeType || input.audio.type });
518
+ ? new File([Uint8Array.from(input.audio)], filename, {
519
+ type: input.mimeType,
520
+ })
521
+ : new File([input.audio], filename, {
522
+ type: input.mimeType || input.audio.type,
523
+ });
390
524
  form.append("audio", audio, filename);
391
525
  form.append("mimeType", input.mimeType);
392
526
  if (input.durationSeconds !== undefined) {
@@ -396,7 +530,10 @@ export class OpenGeniClient {
396
530
  try {
397
531
  response = await this.fetchImpl(this.url(`/v1/workspaces/${workspaceId}/transcriptions`), {
398
532
  method: "POST",
399
- headers: { ...this.headers(correlationId), Accept: "application/json" },
533
+ headers: {
534
+ ...this.headers(correlationId),
535
+ Accept: "application/json",
536
+ },
400
537
  body: form,
401
538
  ...(input.signal ? { signal: input.signal } : {}),
402
539
  });
@@ -405,7 +542,11 @@ export class OpenGeniClient {
405
542
  throw mutationTransportError(correlationId);
406
543
  }
407
544
  assertApiContractResponse(response);
408
- if (!response.ok) throw await apiErrorFromResponse(response, { method: "POST", correlationId });
545
+ if (!response.ok)
546
+ throw await apiErrorFromResponse(response, {
547
+ method: "POST",
548
+ correlationId,
549
+ });
409
550
  await assertJsonResponse(response, { method: "POST", correlationId });
410
551
  let body: unknown;
411
552
  try {
@@ -509,7 +650,11 @@ export class OpenGeniClient {
509
650
  throw mutationTransportError(correlationId);
510
651
  }
511
652
  assertApiContractResponse(response);
512
- if (!response.ok) throw await apiErrorFromResponse(response, { method: "PUT", correlationId });
653
+ if (!response.ok)
654
+ throw await apiErrorFromResponse(response, {
655
+ method: "PUT",
656
+ correlationId,
657
+ });
513
658
  await assertJsonResponse(response, { method: "PUT", correlationId });
514
659
  const body = await response.json().catch(() => null);
515
660
  if (!isUploadTranscriptionRecordingChunkResponse(body)) {
@@ -603,11 +748,13 @@ export class OpenGeniClient {
603
748
  );
604
749
  }
605
750
 
606
- async getSession(workspaceId: string, sessionId: string): Promise<Session> {
607
- return await this.requestJson<Session>(
608
- "GET",
609
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}`,
610
- );
751
+ async getSession(
752
+ workspaceId: string,
753
+ sessionId: string,
754
+ options: { fresh?: boolean } = {},
755
+ ): Promise<Session> {
756
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}`;
757
+ return await this.singleFlightRead(path, () => this.requestJson<Session>("GET", path), options);
611
758
  }
612
759
 
613
760
  async updateSession(
@@ -740,6 +887,32 @@ export class OpenGeniClient {
740
887
  return response;
741
888
  }
742
889
 
890
+ /** Compact, bounded workspace agent hierarchy page. */
891
+ async listAgentTopology(
892
+ workspaceId: string,
893
+ options: {
894
+ limit?: number;
895
+ parentSessionId?: string | null;
896
+ cursor?: string;
897
+ search?: string;
898
+ } = {},
899
+ ): Promise<AgentTopologyPageResponse> {
900
+ const search = options.search?.trim();
901
+ return await this.requestJson<AgentTopologyPageResponse>(
902
+ "GET",
903
+ `/v1/workspaces/${workspaceId}/agent-topology`,
904
+ undefined,
905
+ {
906
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
907
+ ...(options.parentSessionId === undefined
908
+ ? {}
909
+ : { parentSessionId: options.parentSessionId ?? "null" }),
910
+ ...(options.cursor ? { cursor: options.cursor } : {}),
911
+ ...(search ? { search } : {}),
912
+ },
913
+ );
914
+ }
915
+
743
916
  /** Set this authenticated member's personal workspace pin for a session. */
744
917
  async updateSessionPin(
745
918
  workspaceId: string,
@@ -754,12 +927,40 @@ export class OpenGeniClient {
754
927
  }
755
928
 
756
929
  async getSessionLineage(workspaceId: string, sessionId: string): Promise<SessionLineageResponse> {
757
- return await this.requestJson<SessionLineageResponse>(
758
- "GET",
759
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`,
930
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
931
+ return await this.singleFlightRead(path, () =>
932
+ this.requestJson<SessionLineageResponse>("GET", path),
760
933
  );
761
934
  }
762
935
 
936
+ private singleFlightRead<T>(
937
+ key: string,
938
+ read: () => Promise<T>,
939
+ options: { fresh?: boolean } = {},
940
+ ): Promise<T> {
941
+ const existing = this.readInFlight.get(key);
942
+ if (existing) {
943
+ if (!options.fresh) return existing as Promise<T>;
944
+ const queued = this.readTrailing.get(key);
945
+ if (queued) return queued as Promise<T>;
946
+ const trailing = existing.then(
947
+ () => this.singleFlightRead(key, read),
948
+ () => this.singleFlightRead(key, read),
949
+ );
950
+ this.readTrailing.set(key, trailing);
951
+ const clear = () => {
952
+ if (this.readTrailing.get(key) === trailing) this.readTrailing.delete(key);
953
+ };
954
+ void trailing.then(clear, clear);
955
+ return trailing;
956
+ }
957
+ const promise = read().finally(() => {
958
+ if (this.readInFlight.get(key) === promise) this.readInFlight.delete(key);
959
+ });
960
+ this.readInFlight.set(key, promise);
961
+ return promise;
962
+ }
963
+
763
964
  /** Negotiate one server-mediated connected-Codex GPT-Live V3 WebRTC call. */
764
965
  async negotiateCodexRealtimeWebrtc(
765
966
  workspaceId: string,
@@ -883,7 +1084,9 @@ export class OpenGeniClient {
883
1084
 
884
1085
  /** Newest turn that durably emitted `turn.started`, or null before any admission. */
885
1086
  async getLatestStartedTurn(workspaceId: string, sessionId: string): Promise<SessionTurn | null> {
886
- const turns = await this.listTurns(workspaceId, sessionId, { latestStarted: true });
1087
+ const turns = await this.listTurns(workspaceId, sessionId, {
1088
+ latestStarted: true,
1089
+ });
887
1090
  return turns[0] ?? null;
888
1091
  }
889
1092
 
@@ -928,6 +1131,24 @@ export class OpenGeniClient {
928
1131
  return response.samples;
929
1132
  }
930
1133
 
1134
+ /**
1135
+ * Remove one connected self-hosted machine enrollment. The control-plane
1136
+ * operation works while the agent is offline, revokes future reconnects,
1137
+ * retains history, and returns a typed blocker when active route/lease or
1138
+ * recovery dependencies make removal unsafe. `idempotencyKey` is replay-safe.
1139
+ */
1140
+ async removeEnrollment(
1141
+ workspaceId: string,
1142
+ enrollmentId: string,
1143
+ request: RemoveEnrollmentRequest = {},
1144
+ ): Promise<RemoveEnrollmentResponse> {
1145
+ return await this.requestJson<RemoveEnrollmentResponse>(
1146
+ "POST",
1147
+ `/v1/workspaces/${workspaceId}/enrollments/${enrollmentId}/revoke`,
1148
+ request,
1149
+ );
1150
+ }
1151
+
931
1152
  // --- Self-hosted enrollment UX (design 11) --------------------------------
932
1153
 
933
1154
  /**
@@ -1350,9 +1571,9 @@ export class OpenGeniClient {
1350
1571
  // --- Turn queue ------------------------------------------------------------
1351
1572
 
1352
1573
  async getQueue(workspaceId: string, sessionId: string): Promise<SessionQueueSnapshot> {
1353
- return await this.requestJson<SessionQueueSnapshot>(
1354
- "GET",
1355
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`,
1574
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/queue`;
1575
+ return await this.singleFlightRead(path, () =>
1576
+ this.requestJson<SessionQueueSnapshot>("GET", path),
1356
1577
  );
1357
1578
  }
1358
1579
 
@@ -1469,7 +1690,11 @@ export class OpenGeniClient {
1469
1690
  async cancelSession(
1470
1691
  workspaceId: string,
1471
1692
  sessionId: string,
1472
- options: { reason?: string; clientEventId?: string; expectedControlEtag?: string } = {},
1693
+ options: {
1694
+ reason?: string;
1695
+ clientEventId?: string;
1696
+ expectedControlEtag?: string;
1697
+ } = {},
1473
1698
  ): Promise<SessionControlResponse> {
1474
1699
  return await this.controlSession(workspaceId, sessionId, {
1475
1700
  action: "cancel",
@@ -1614,10 +1839,8 @@ export class OpenGeniClient {
1614
1839
 
1615
1840
  /** The session's goal. 404s when the session never had one. */
1616
1841
  async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
1617
- return await this.requestJson<SessionGoal>(
1618
- "GET",
1619
- `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`,
1620
- );
1842
+ const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
1843
+ return await this.singleFlightRead(path, () => this.requestJson<SessionGoal>("GET", path));
1621
1844
  }
1622
1845
 
1623
1846
  async updateGoal(
@@ -1703,6 +1926,22 @@ export class OpenGeniClient {
1703
1926
  );
1704
1927
  }
1705
1928
 
1929
+ /** FileSystem: hydrate several independent directories behind one sandbox lease. */
1930
+ async fsListBatch(
1931
+ workspaceId: string,
1932
+ sessionId: string,
1933
+ request: FsListBatchRequest,
1934
+ options: OpenGeniRequestOptions = {},
1935
+ ): Promise<FsListBatchResponse> {
1936
+ return await this.requestJson<FsListBatchResponse>(
1937
+ "POST",
1938
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/fs/list-batch`,
1939
+ request,
1940
+ {},
1941
+ options,
1942
+ );
1943
+ }
1944
+
1706
1945
  /** FileSystem: read a file (text or base64; binary-safe, size-capped). */
1707
1946
  async fsRead(
1708
1947
  workspaceId: string,
@@ -1803,6 +2042,22 @@ export class OpenGeniClient {
1803
2042
  );
1804
2043
  }
1805
2044
 
2045
+ /** Git: read status and optional diffs for several repositories behind one sandbox lease. */
2046
+ async gitReadBatch(
2047
+ workspaceId: string,
2048
+ sessionId: string,
2049
+ request: GitReadBatchRequest,
2050
+ options: OpenGeniRequestOptions = {},
2051
+ ): Promise<GitReadBatchResponse> {
2052
+ return await this.requestJson<GitReadBatchResponse>(
2053
+ "POST",
2054
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/git/read-batch`,
2055
+ request,
2056
+ {},
2057
+ options,
2058
+ );
2059
+ }
2060
+
1806
2061
  /** Git: commit log. */
1807
2062
  async gitLog(
1808
2063
  workspaceId: string,
@@ -1976,13 +2231,11 @@ export class OpenGeniClient {
1976
2231
  }
1977
2232
 
1978
2233
  /** Attach a viewer holder (refcounted liveness — keeps the box warm while
1979
- * watched/used), spinning the box up in-process when cold, and mint the scoped
1980
- * direct-to-provider URLs for the requested plane(s). `request.desktop:true`
1981
- * opts into the un-redacted pixel plane and mints the noVNC URL — that plane
1982
- * alone throws `OpenGeniApiError(409)` when the un-redacted/shared
1983
- * acknowledgment is missing (the consent gate). A terminal-only attach
1984
- * (`desktop` omitted/false) warms the box + mints the pty-ws terminal cell with
1985
- * NO consent gate. An omitted `viewerId` mints a fresh one. */
2234
+ * watched/used), spinning the box up in-process when cold. Exact plane flags
2235
+ * mint only the requested short-lived credentials and enforce that plane's
2236
+ * permission. `desktop:true` alone carries the un-redacted/shared consent
2237
+ * gate. An entirely omitted plane set retains the legacy terminal-only
2238
+ * meaning; current clients should send all three flags. */
1986
2239
  async attachViewer(
1987
2240
  workspaceId: string,
1988
2241
  sessionId: string,
@@ -2019,184 +2272,669 @@ export class OpenGeniClient {
2019
2272
  );
2020
2273
  }
2021
2274
 
2022
- // --- Access + workspaces -----------------------------------------------------
2023
-
2024
- /**
2025
- * The deployment's public client bootstrap config: the host-exposed models
2026
- * (provider-grouped in `models`, flat in `allowedModels` for back-compat),
2027
- * reasoning efforts, MCP servers, file-upload limits, and how the client is
2028
- * expected to authenticate. Drives a composer's model picker without prior
2029
- * knowledge of the host setup; safe to call before any auth is established.
2030
- */
2031
- async getClientConfig(): Promise<ClientConfig> {
2032
- const config = await this.requestJson<ClientConfig>("GET", "/v1/config/client");
2033
- if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
2034
- throw new OpenGeniApiContractMismatchError(
2035
- OPENGENI_API_CONTRACT_REVISION,
2036
- String(config.apiContractRevision || "(missing)"),
2037
- );
2038
- }
2039
- return config;
2040
- }
2275
+ // --- Browser / Computer interaction resources --------------------------------
2041
2276
 
2042
- /** Authenticated model definitions plus workspace-specific selectability. */
2043
- async getWorkspaceModelCatalog(workspaceId: string): Promise<WorkspaceModelCatalogResponse> {
2044
- return await this.requestJson<WorkspaceModelCatalogResponse>(
2277
+ async listAttachedBrowsers(
2278
+ workspaceId: string,
2279
+ options: AttachedBrowserDeviceListOptions = {},
2280
+ ): Promise<AttachedBrowserDeviceListResponse> {
2281
+ return await this.requestJson<AttachedBrowserDeviceListResponse>(
2045
2282
  "GET",
2046
- `/v1/workspaces/${workspaceId}/model-catalog`,
2283
+ `/v1/workspaces/${workspaceId}/attached-browsers`,
2284
+ undefined,
2285
+ options.includeDisconnected ? { includeDisconnected: "true" } : {},
2286
+ options,
2047
2287
  );
2048
2288
  }
2049
2289
 
2050
- /** Authenticated realtime voice models and credential readiness. */
2051
- async getWorkspaceRealtimeModelCatalog(
2290
+ async getAttachedBrowser(
2052
2291
  workspaceId: string,
2053
- ): Promise<WorkspaceRealtimeModelCatalogResponse> {
2054
- return await this.requestJson<WorkspaceRealtimeModelCatalogResponse>(
2292
+ deviceId: string,
2293
+ options: OpenGeniRequestOptions = {},
2294
+ ): Promise<AttachedBrowserDevice> {
2295
+ return await this.requestJson<AttachedBrowserDevice>(
2055
2296
  "GET",
2056
- `/v1/workspaces/${workspaceId}/realtime-model-catalog`,
2297
+ `/v1/workspaces/${workspaceId}/attached-browsers/${encodeURIComponent(deviceId)}`,
2298
+ undefined,
2299
+ {},
2300
+ options,
2057
2301
  );
2058
2302
  }
2059
2303
 
2060
- /** The caller's access context: subject, account + workspace grants, defaults. */
2061
- async getAccessContext(): Promise<AccessContext> {
2062
- return await this.requestJson<AccessContext>("GET", "/v1/access/me");
2063
- }
2064
-
2065
- async listWorkspaces(): Promise<Workspace[]> {
2066
- return await this.requestJson<Workspace[]>("GET", "/v1/workspaces");
2067
- }
2068
-
2069
- async createWorkspace(request: CreateWorkspaceRequest): Promise<Workspace> {
2070
- return await this.requestJson<Workspace>("POST", "/v1/workspaces", request);
2071
- }
2072
-
2073
- async getWorkspace(workspaceId: string): Promise<Workspace> {
2074
- return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
2304
+ async listBrowserIdentities(
2305
+ workspaceId: string,
2306
+ options: BrowserIdentityListOptions = {},
2307
+ ): Promise<BrowserIdentityListResponse> {
2308
+ return await this.requestJson<BrowserIdentityListResponse>(
2309
+ "GET",
2310
+ `/v1/workspaces/${workspaceId}/browser-identities`,
2311
+ undefined,
2312
+ options.includeArchived ? { includeArchived: "true" } : {},
2313
+ options,
2314
+ );
2075
2315
  }
2076
2316
 
2077
- /** Read-time, secret-safe inventory of policy heads and visible workspace knowledge. */
2078
- async getWorkspaceState(
2317
+ async getBrowserIdentity(
2079
2318
  workspaceId: string,
2080
- options: WorkspaceStateGetOptions = {},
2081
- ): Promise<WorkspaceStateResponse> {
2082
- const params = new URLSearchParams();
2083
- if (options.attemptId) params.set("attemptId", options.attemptId);
2084
- const query = params.size > 0 ? `?${params.toString()}` : "";
2085
- return await this.requestJson<WorkspaceStateResponse>(
2319
+ identityId: string,
2320
+ options: OpenGeniRequestOptions = {},
2321
+ ): Promise<BrowserIdentity> {
2322
+ return await this.requestJson<BrowserIdentity>(
2086
2323
  "GET",
2087
- `/v1/workspaces/${workspaceId}/workspace-state${query}`,
2324
+ `/v1/workspaces/${workspaceId}/browser-identities/${encodeURIComponent(identityId)}`,
2325
+ undefined,
2326
+ {},
2327
+ options,
2088
2328
  );
2089
2329
  }
2090
2330
 
2091
- /** Download the canonical, explicitly sanitized Workspace State export. */
2092
- async exportWorkspaceState(
2331
+ async createBrowserIdentity(
2093
2332
  workspaceId: string,
2094
- options: WorkspaceStateGetOptions = {},
2095
- ): Promise<WorkspaceStateExportResponse> {
2096
- const params = new URLSearchParams();
2097
- if (options.attemptId) params.set("attemptId", options.attemptId);
2098
- const query = params.size > 0 ? `?${params.toString()}` : "";
2099
- return await this.requestJson<WorkspaceStateExportResponse>(
2100
- "GET",
2101
- `/v1/workspaces/${workspaceId}/workspace-state/export${query}`,
2333
+ request: CreateBrowserIdentityRequest,
2334
+ options: OpenGeniRequestOptions = {},
2335
+ ): Promise<BrowserIdentityMutationResponse> {
2336
+ return await this.requestJson<BrowserIdentityMutationResponse>(
2337
+ "POST",
2338
+ `/v1/workspaces/${workspaceId}/browser-identities`,
2339
+ request,
2340
+ {},
2341
+ options,
2102
2342
  );
2103
2343
  }
2104
2344
 
2105
- async updateWorkspace(workspaceId: string, request: UpdateWorkspaceRequest): Promise<Workspace> {
2106
- return await this.requestJson<Workspace>("PATCH", `/v1/workspaces/${workspaceId}`, request);
2345
+ async listBrowserRevisions(
2346
+ workspaceId: string,
2347
+ identityId: string,
2348
+ options: OpenGeniRequestOptions = {},
2349
+ ): Promise<BrowserRevisionListResponse> {
2350
+ return await this.requestJson<BrowserRevisionListResponse>(
2351
+ "GET",
2352
+ `/v1/workspaces/${workspaceId}/browser-identities/${encodeURIComponent(identityId)}/revisions`,
2353
+ undefined,
2354
+ {},
2355
+ options,
2356
+ );
2107
2357
  }
2108
2358
 
2109
- /** Inspect immutable instruction-policy history, active heads, and activation audit evidence. */
2110
- async listWorkspaceInstructionPolicies(
2359
+ /** Workspace-wide BrowserSession inventory. Associations express relevance,
2360
+ * not access: peers and child agents intentionally remain discoverable. */
2361
+ async listBrowserSessions(
2111
2362
  workspaceId: string,
2112
- options: WorkspaceInstructionPolicyListOptions = {},
2113
- ): Promise<WorkspaceInstructionPolicyListResponse> {
2114
- const params = new URLSearchParams();
2115
- if (options.kind !== undefined) params.set("kind", options.kind);
2116
- if (options.scope !== undefined) params.set("scope", options.scope);
2117
- if (options.roleKey !== undefined) params.set("roleKey", options.roleKey);
2118
- if (options.afterRevision !== undefined) {
2119
- params.set("afterRevision", String(options.afterRevision));
2120
- }
2121
- if (options.limit !== undefined) params.set("limit", String(options.limit));
2122
- const query = params.toString();
2123
- return await this.requestJson<WorkspaceInstructionPolicyListResponse>(
2363
+ options: OpenGeniRequestOptions = {},
2364
+ ): Promise<BrowserSessionListResponse> {
2365
+ return await this.requestJson<BrowserSessionListResponse>(
2124
2366
  "GET",
2125
- `/v1/workspaces/${workspaceId}/instruction-policies${query ? `?${query}` : ""}`,
2367
+ `/v1/workspaces/${workspaceId}/browser-sessions`,
2368
+ undefined,
2369
+ {},
2370
+ options,
2126
2371
  );
2127
2372
  }
2128
2373
 
2129
- async getWorkspaceInstructionPolicyRevision(
2374
+ async getBrowserSession(
2130
2375
  workspaceId: string,
2131
- revisionId: string,
2132
- ): Promise<WorkspaceInstructionPolicyRevision> {
2133
- return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2376
+ browserSessionId: string,
2377
+ options: OpenGeniRequestOptions = {},
2378
+ ): Promise<BrowserSession> {
2379
+ return await this.requestJson<BrowserSession>(
2134
2380
  "GET",
2135
- `/v1/workspaces/${workspaceId}/instruction-policies/${encodeURIComponent(revisionId)}`,
2381
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}`,
2382
+ undefined,
2383
+ {},
2384
+ options,
2136
2385
  );
2137
2386
  }
2138
2387
 
2139
- async createWorkspaceInstructionPolicyDraft(
2388
+ async createBrowserSession(
2140
2389
  workspaceId: string,
2141
- request: CreateWorkspaceInstructionPolicyDraftRequest,
2142
- ): Promise<WorkspaceInstructionPolicyRevision> {
2143
- return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2390
+ request: CreateBrowserSessionRequest,
2391
+ options: OpenGeniRequestOptions = {},
2392
+ ): Promise<BrowserSessionMutationResponse> {
2393
+ return await this.requestJson<BrowserSessionMutationResponse>(
2144
2394
  "POST",
2145
- `/v1/workspaces/${workspaceId}/instruction-policies/drafts`,
2395
+ `/v1/workspaces/${workspaceId}/browser-sessions`,
2146
2396
  request,
2397
+ {},
2398
+ options,
2147
2399
  );
2148
2400
  }
2149
2401
 
2150
- /** List the newest immutable onboarding proposals and their inactive policy drafts. */
2151
- async listWorkspaceInstructionPolicyOnboardingProposals(
2402
+ async listBrowserTargets(
2152
2403
  workspaceId: string,
2153
- options: WorkspaceInstructionPolicyOnboardingProposalListOptions = {},
2154
- ): Promise<WorkspaceInstructionPolicyOnboardingProposalListResponse> {
2155
- const params = new URLSearchParams();
2156
- if (options.limit !== undefined) params.set("limit", String(options.limit));
2157
- const query = params.toString();
2158
- return await this.requestJson<WorkspaceInstructionPolicyOnboardingProposalListResponse>(
2404
+ browserSessionId: string,
2405
+ options: OpenGeniRequestOptions = {},
2406
+ ): Promise<BrowserTargetListResponse> {
2407
+ return await this.requestJson<BrowserTargetListResponse>(
2159
2408
  "GET",
2160
- `/v1/workspaces/${workspaceId}/instruction-policies/onboarding-proposals${query ? `?${query}` : ""}`,
2409
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets`,
2410
+ undefined,
2411
+ {},
2412
+ options,
2161
2413
  );
2162
2414
  }
2163
2415
 
2164
- /** Create one draft-only proposal against an exact active-policy baseline. */
2165
- async createWorkspaceInstructionPolicyOnboardingProposal(
2416
+ async openBrowserTarget(
2166
2417
  workspaceId: string,
2167
- request: CreateWorkspaceInstructionPolicyOnboardingProposalRequest,
2168
- ): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
2169
- return await this.requestJson<WorkspaceInstructionPolicyOnboardingProposal>(
2418
+ browserSessionId: string,
2419
+ request: BrowserOpenTargetRequest = {},
2420
+ options: OpenGeniRequestOptions = {},
2421
+ ): Promise<BrowserTarget> {
2422
+ return await this.requestJson<BrowserTarget>(
2170
2423
  "POST",
2171
- `/v1/workspaces/${workspaceId}/instruction-policies/onboarding-proposals`,
2424
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets`,
2172
2425
  request,
2426
+ {},
2427
+ options,
2173
2428
  );
2174
2429
  }
2175
2430
 
2176
- /** Import the stored legacy workspace override as an inactive charter draft. */
2177
- async importLegacyWorkspaceInstructionPolicyDraft(
2431
+ async selectBrowserTarget(
2178
2432
  workspaceId: string,
2179
- request: ImportLegacyWorkspaceInstructionPolicyDraftRequest = {},
2180
- ): Promise<WorkspaceInstructionPolicyRevision> {
2181
- return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2433
+ browserSessionId: string,
2434
+ targetId: string,
2435
+ options: OpenGeniRequestOptions = {},
2436
+ ): Promise<BrowserTarget> {
2437
+ return await this.requestJson<BrowserTarget>(
2182
2438
  "POST",
2183
- `/v1/workspaces/${workspaceId}/instruction-policies/import-legacy`,
2184
- request,
2439
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets/${encodeURIComponent(targetId)}/select`,
2440
+ {},
2441
+ {},
2442
+ options,
2185
2443
  );
2186
2444
  }
2187
2445
 
2188
- async diffWorkspaceInstructionPolicyRevisions(
2446
+ async closeBrowserTarget(
2189
2447
  workspaceId: string,
2190
- request: WorkspaceInstructionPolicyDiffRequest,
2191
- ): Promise<WorkspaceInstructionPolicyDiffResponse> {
2192
- const params = new URLSearchParams({
2193
- fromRevisionId: request.fromRevisionId,
2194
- toRevisionId: request.toRevisionId,
2195
- });
2196
- return await this.requestJson<WorkspaceInstructionPolicyDiffResponse>(
2197
- "GET",
2198
- `/v1/workspaces/${workspaceId}/instruction-policies/diff?${params}`,
2199
- );
2448
+ browserSessionId: string,
2449
+ targetId: string,
2450
+ options: OpenGeniRequestOptions = {},
2451
+ ): Promise<BrowserTargetListResponse> {
2452
+ return await this.requestJson<BrowserTargetListResponse>(
2453
+ "DELETE",
2454
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets/${encodeURIComponent(targetId)}`,
2455
+ undefined,
2456
+ {},
2457
+ options,
2458
+ );
2459
+ }
2460
+
2461
+ async observeBrowserTarget(
2462
+ workspaceId: string,
2463
+ browserSessionId: string,
2464
+ targetId: string,
2465
+ options: OpenGeniRequestOptions = {},
2466
+ ): Promise<BrowserObservation> {
2467
+ return await this.requestJson<BrowserObservation>(
2468
+ "GET",
2469
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets/${encodeURIComponent(targetId)}/observation`,
2470
+ undefined,
2471
+ {},
2472
+ options,
2473
+ );
2474
+ }
2475
+
2476
+ async actInBrowser(
2477
+ workspaceId: string,
2478
+ browserSessionId: string,
2479
+ request: BrowserActionRequest,
2480
+ options: OpenGeniRequestOptions = {},
2481
+ ): Promise<BrowserActionReceipt> {
2482
+ return await this.requestJson<BrowserActionReceipt>(
2483
+ "POST",
2484
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/actions`,
2485
+ request,
2486
+ {},
2487
+ options,
2488
+ );
2489
+ }
2490
+
2491
+ async getBrowserActionReceipt(
2492
+ workspaceId: string,
2493
+ browserSessionId: string,
2494
+ operationId: string,
2495
+ options: OpenGeniRequestOptions = {},
2496
+ ): Promise<BrowserActionReceipt> {
2497
+ return await this.requestJson<BrowserActionReceipt>(
2498
+ "GET",
2499
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/operations/${encodeURIComponent(operationId)}`,
2500
+ undefined,
2501
+ {},
2502
+ options,
2503
+ );
2504
+ }
2505
+
2506
+ async listBrowserDiagnostics(
2507
+ workspaceId: string,
2508
+ browserSessionId: string,
2509
+ targetId: string,
2510
+ options: BrowserDiagnosticsOptions = {},
2511
+ ): Promise<BrowserDiagnosticBatch> {
2512
+ return await this.requestJson<BrowserDiagnosticBatch>(
2513
+ "GET",
2514
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/targets/${encodeURIComponent(targetId)}/diagnostics`,
2515
+ undefined,
2516
+ {
2517
+ ...(options.kinds?.length ? { kinds: options.kinds.join(",") } : {}),
2518
+ ...(options.after !== undefined ? { after: String(options.after) } : {}),
2519
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
2520
+ },
2521
+ options.signal ? { signal: options.signal } : {},
2522
+ );
2523
+ }
2524
+
2525
+ async attachBrowserSession(
2526
+ workspaceId: string,
2527
+ browserSessionId: string,
2528
+ request: BrowserSessionAttachmentRequest,
2529
+ options: OpenGeniRequestOptions = {},
2530
+ ): Promise<BrowserSessionAttachment> {
2531
+ return await this.requestJson<BrowserSessionAttachment>(
2532
+ "POST",
2533
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/attachments`,
2534
+ request,
2535
+ {},
2536
+ options,
2537
+ );
2538
+ }
2539
+
2540
+ async heartbeatBrowserSession(
2541
+ workspaceId: string,
2542
+ browserSessionId: string,
2543
+ options: OpenGeniRequestOptions = {},
2544
+ ): Promise<BrowserSessionHeartbeatResponse> {
2545
+ return await this.requestJson<BrowserSessionHeartbeatResponse>(
2546
+ "POST",
2547
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/heartbeat`,
2548
+ {},
2549
+ {},
2550
+ options,
2551
+ );
2552
+ }
2553
+
2554
+ async publishBrowserRevision(
2555
+ workspaceId: string,
2556
+ browserSessionId: string,
2557
+ request: PublishBrowserRevisionRequest,
2558
+ options: OpenGeniRequestOptions = {},
2559
+ ): Promise<PublishBrowserRevisionResponse> {
2560
+ return await this.requestJson<PublishBrowserRevisionResponse>(
2561
+ "POST",
2562
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/revisions`,
2563
+ request,
2564
+ {},
2565
+ options,
2566
+ );
2567
+ }
2568
+
2569
+ async suspendBrowserSession(
2570
+ workspaceId: string,
2571
+ browserSessionId: string,
2572
+ request: BrowserSessionLifecycleRequest,
2573
+ options: OpenGeniRequestOptions = {},
2574
+ ): Promise<BrowserSessionMutationResponse> {
2575
+ return await this.requestJson<BrowserSessionMutationResponse>(
2576
+ "POST",
2577
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/suspend`,
2578
+ request,
2579
+ {},
2580
+ options,
2581
+ );
2582
+ }
2583
+
2584
+ async resumeBrowserSession(
2585
+ workspaceId: string,
2586
+ browserSessionId: string,
2587
+ request: BrowserSessionLifecycleRequest,
2588
+ options: OpenGeniRequestOptions = {},
2589
+ ): Promise<BrowserSessionMutationResponse> {
2590
+ return await this.requestJson<BrowserSessionMutationResponse>(
2591
+ "POST",
2592
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/resume`,
2593
+ request,
2594
+ {},
2595
+ options,
2596
+ );
2597
+ }
2598
+
2599
+ async endBrowserSession(
2600
+ workspaceId: string,
2601
+ browserSessionId: string,
2602
+ request: BrowserSessionLifecycleRequest,
2603
+ options: OpenGeniRequestOptions = {},
2604
+ ): Promise<BrowserSessionMutationResponse> {
2605
+ return await this.requestJson<BrowserSessionMutationResponse>(
2606
+ "POST",
2607
+ `/v1/workspaces/${workspaceId}/browser-sessions/${encodeURIComponent(browserSessionId)}/end`,
2608
+ request,
2609
+ {},
2610
+ options,
2611
+ );
2612
+ }
2613
+
2614
+ /** Workspace-wide ComputerSession inventory. Associations express
2615
+ * relevance, not access; peer sessions intentionally remain discoverable. */
2616
+ async listComputerSessions(
2617
+ workspaceId: string,
2618
+ options: OpenGeniRequestOptions = {},
2619
+ ): Promise<ComputerSessionListResponse> {
2620
+ return await this.requestJson<ComputerSessionListResponse>(
2621
+ "GET",
2622
+ `/v1/workspaces/${workspaceId}/computer-sessions`,
2623
+ undefined,
2624
+ {},
2625
+ options,
2626
+ );
2627
+ }
2628
+
2629
+ async getComputerSession(
2630
+ workspaceId: string,
2631
+ computerSessionId: string,
2632
+ options: OpenGeniRequestOptions = {},
2633
+ ): Promise<ComputerSession> {
2634
+ return await this.requestJson<ComputerSession>(
2635
+ "GET",
2636
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}`,
2637
+ undefined,
2638
+ {},
2639
+ options,
2640
+ );
2641
+ }
2642
+
2643
+ async createComputerSession(
2644
+ workspaceId: string,
2645
+ request: CreateComputerSessionRequest,
2646
+ options: OpenGeniRequestOptions = {},
2647
+ ): Promise<ComputerSessionMutationResponse> {
2648
+ return await this.requestJson<ComputerSessionMutationResponse>(
2649
+ "POST",
2650
+ `/v1/workspaces/${workspaceId}/computer-sessions`,
2651
+ request,
2652
+ {},
2653
+ options,
2654
+ );
2655
+ }
2656
+
2657
+ async listComputerTargets(
2658
+ workspaceId: string,
2659
+ computerSessionId: string,
2660
+ options: OpenGeniRequestOptions = {},
2661
+ ): Promise<ComputerTargetListResponse> {
2662
+ return await this.requestJson<ComputerTargetListResponse>(
2663
+ "GET",
2664
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/targets`,
2665
+ undefined,
2666
+ {},
2667
+ options,
2668
+ );
2669
+ }
2670
+
2671
+ async observeComputerTarget(
2672
+ workspaceId: string,
2673
+ computerSessionId: string,
2674
+ targetId: string,
2675
+ options: OpenGeniRequestOptions = {},
2676
+ ): Promise<ComputerObservation> {
2677
+ return await this.requestJson<ComputerObservation>(
2678
+ "GET",
2679
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/targets/${encodeURIComponent(targetId)}/observation`,
2680
+ undefined,
2681
+ {},
2682
+ options,
2683
+ );
2684
+ }
2685
+
2686
+ async actInComputer(
2687
+ workspaceId: string,
2688
+ computerSessionId: string,
2689
+ request: ComputerActionRequest,
2690
+ options: OpenGeniRequestOptions = {},
2691
+ ): Promise<ComputerActionReceipt> {
2692
+ return await this.requestJson<ComputerActionReceipt>(
2693
+ "POST",
2694
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/actions`,
2695
+ request,
2696
+ {},
2697
+ options,
2698
+ );
2699
+ }
2700
+
2701
+ async getComputerActionReceipt(
2702
+ workspaceId: string,
2703
+ computerSessionId: string,
2704
+ operationId: string,
2705
+ options: OpenGeniRequestOptions = {},
2706
+ ): Promise<ComputerActionReceipt> {
2707
+ return await this.requestJson<ComputerActionReceipt>(
2708
+ "GET",
2709
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/operations/${encodeURIComponent(operationId)}`,
2710
+ undefined,
2711
+ {},
2712
+ options,
2713
+ );
2714
+ }
2715
+
2716
+ async attachComputerSession(
2717
+ workspaceId: string,
2718
+ computerSessionId: string,
2719
+ request: ComputerSessionAttachmentRequest,
2720
+ options: OpenGeniRequestOptions = {},
2721
+ ): Promise<ComputerSessionAttachment> {
2722
+ return await this.requestJson<ComputerSessionAttachment>(
2723
+ "POST",
2724
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/attachments`,
2725
+ request,
2726
+ {},
2727
+ options,
2728
+ );
2729
+ }
2730
+
2731
+ async heartbeatComputerSession(
2732
+ workspaceId: string,
2733
+ computerSessionId: string,
2734
+ options: OpenGeniRequestOptions = {},
2735
+ ): Promise<ComputerSessionHeartbeatResponse> {
2736
+ return await this.requestJson<ComputerSessionHeartbeatResponse>(
2737
+ "POST",
2738
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/heartbeat`,
2739
+ {},
2740
+ {},
2741
+ options,
2742
+ );
2743
+ }
2744
+
2745
+ async endComputerSession(
2746
+ workspaceId: string,
2747
+ computerSessionId: string,
2748
+ request: ComputerSessionLifecycleRequest,
2749
+ options: OpenGeniRequestOptions = {},
2750
+ ): Promise<ComputerSessionMutationResponse> {
2751
+ return await this.requestJson<ComputerSessionMutationResponse>(
2752
+ "POST",
2753
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/end`,
2754
+ request,
2755
+ {},
2756
+ options,
2757
+ );
2758
+ }
2759
+
2760
+ // --- Access + workspaces -----------------------------------------------------
2761
+
2762
+ /**
2763
+ * The deployment's public client bootstrap config: the host-exposed models
2764
+ * (provider-grouped in `models`, flat in `allowedModels` for back-compat),
2765
+ * reasoning efforts, MCP servers, file-upload limits, and how the client is
2766
+ * expected to authenticate. Drives a composer's model picker without prior
2767
+ * knowledge of the host setup; safe to call before any auth is established.
2768
+ */
2769
+ async getClientConfig(): Promise<ClientConfig> {
2770
+ const config = await this.requestJson<ClientConfig>("GET", "/v1/config/client");
2771
+ if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
2772
+ throw new OpenGeniApiContractMismatchError(
2773
+ OPENGENI_API_CONTRACT_REVISION,
2774
+ String(config.apiContractRevision || "(missing)"),
2775
+ );
2776
+ }
2777
+ return config;
2778
+ }
2779
+
2780
+ /** Authenticated model definitions plus workspace-specific selectability. */
2781
+ async getWorkspaceModelCatalog(workspaceId: string): Promise<WorkspaceModelCatalogResponse> {
2782
+ return await this.requestJson<WorkspaceModelCatalogResponse>(
2783
+ "GET",
2784
+ `/v1/workspaces/${workspaceId}/model-catalog`,
2785
+ );
2786
+ }
2787
+
2788
+ /** Authenticated realtime voice models and credential readiness. */
2789
+ async getWorkspaceRealtimeModelCatalog(
2790
+ workspaceId: string,
2791
+ ): Promise<WorkspaceRealtimeModelCatalogResponse> {
2792
+ return await this.requestJson<WorkspaceRealtimeModelCatalogResponse>(
2793
+ "GET",
2794
+ `/v1/workspaces/${workspaceId}/realtime-model-catalog`,
2795
+ );
2796
+ }
2797
+
2798
+ /** The caller's access context: subject, account + workspace grants, defaults. */
2799
+ async getAccessContext(): Promise<AccessContext> {
2800
+ return await this.requestJson<AccessContext>("GET", "/v1/access/me");
2801
+ }
2802
+
2803
+ async listWorkspaces(): Promise<Workspace[]> {
2804
+ return await this.requestJson<Workspace[]>("GET", "/v1/workspaces");
2805
+ }
2806
+
2807
+ async createWorkspace(request: CreateWorkspaceRequest): Promise<Workspace> {
2808
+ return await this.requestJson<Workspace>("POST", "/v1/workspaces", request);
2809
+ }
2810
+
2811
+ async getWorkspace(workspaceId: string): Promise<Workspace> {
2812
+ return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
2813
+ }
2814
+
2815
+ /** Read-time, secret-safe inventory of policy heads and visible workspace knowledge. */
2816
+ async getWorkspaceState(
2817
+ workspaceId: string,
2818
+ options: WorkspaceStateGetOptions = {},
2819
+ ): Promise<WorkspaceStateResponse> {
2820
+ const params = new URLSearchParams();
2821
+ if (options.attemptId) params.set("attemptId", options.attemptId);
2822
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2823
+ return await this.requestJson<WorkspaceStateResponse>(
2824
+ "GET",
2825
+ `/v1/workspaces/${workspaceId}/workspace-state${query}`,
2826
+ );
2827
+ }
2828
+
2829
+ /** Download the canonical, explicitly sanitized Workspace State export. */
2830
+ async exportWorkspaceState(
2831
+ workspaceId: string,
2832
+ options: WorkspaceStateGetOptions = {},
2833
+ ): Promise<WorkspaceStateExportResponse> {
2834
+ const params = new URLSearchParams();
2835
+ if (options.attemptId) params.set("attemptId", options.attemptId);
2836
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2837
+ return await this.requestJson<WorkspaceStateExportResponse>(
2838
+ "GET",
2839
+ `/v1/workspaces/${workspaceId}/workspace-state/export${query}`,
2840
+ );
2841
+ }
2842
+
2843
+ async updateWorkspace(workspaceId: string, request: UpdateWorkspaceRequest): Promise<Workspace> {
2844
+ return await this.requestJson<Workspace>("PATCH", `/v1/workspaces/${workspaceId}`, request);
2845
+ }
2846
+
2847
+ /** Inspect immutable instruction-policy history, active heads, and activation audit evidence. */
2848
+ async listWorkspaceInstructionPolicies(
2849
+ workspaceId: string,
2850
+ options: WorkspaceInstructionPolicyListOptions = {},
2851
+ ): Promise<WorkspaceInstructionPolicyListResponse> {
2852
+ const params = new URLSearchParams();
2853
+ if (options.kind !== undefined) params.set("kind", options.kind);
2854
+ if (options.scope !== undefined) params.set("scope", options.scope);
2855
+ if (options.roleKey !== undefined) params.set("roleKey", options.roleKey);
2856
+ if (options.afterRevision !== undefined) {
2857
+ params.set("afterRevision", String(options.afterRevision));
2858
+ }
2859
+ if (options.limit !== undefined) params.set("limit", String(options.limit));
2860
+ const query = params.toString();
2861
+ return await this.requestJson<WorkspaceInstructionPolicyListResponse>(
2862
+ "GET",
2863
+ `/v1/workspaces/${workspaceId}/instruction-policies${query ? `?${query}` : ""}`,
2864
+ );
2865
+ }
2866
+
2867
+ async getWorkspaceInstructionPolicyRevision(
2868
+ workspaceId: string,
2869
+ revisionId: string,
2870
+ ): Promise<WorkspaceInstructionPolicyRevision> {
2871
+ return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2872
+ "GET",
2873
+ `/v1/workspaces/${workspaceId}/instruction-policies/${encodeURIComponent(revisionId)}`,
2874
+ );
2875
+ }
2876
+
2877
+ async createWorkspaceInstructionPolicyDraft(
2878
+ workspaceId: string,
2879
+ request: CreateWorkspaceInstructionPolicyDraftRequest,
2880
+ ): Promise<WorkspaceInstructionPolicyRevision> {
2881
+ return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2882
+ "POST",
2883
+ `/v1/workspaces/${workspaceId}/instruction-policies/drafts`,
2884
+ request,
2885
+ );
2886
+ }
2887
+
2888
+ /** List the newest immutable onboarding proposals and their inactive policy drafts. */
2889
+ async listWorkspaceInstructionPolicyOnboardingProposals(
2890
+ workspaceId: string,
2891
+ options: WorkspaceInstructionPolicyOnboardingProposalListOptions = {},
2892
+ ): Promise<WorkspaceInstructionPolicyOnboardingProposalListResponse> {
2893
+ const params = new URLSearchParams();
2894
+ if (options.limit !== undefined) params.set("limit", String(options.limit));
2895
+ const query = params.toString();
2896
+ return await this.requestJson<WorkspaceInstructionPolicyOnboardingProposalListResponse>(
2897
+ "GET",
2898
+ `/v1/workspaces/${workspaceId}/instruction-policies/onboarding-proposals${query ? `?${query}` : ""}`,
2899
+ );
2900
+ }
2901
+
2902
+ /** Create one draft-only proposal against an exact active-policy baseline. */
2903
+ async createWorkspaceInstructionPolicyOnboardingProposal(
2904
+ workspaceId: string,
2905
+ request: CreateWorkspaceInstructionPolicyOnboardingProposalRequest,
2906
+ ): Promise<WorkspaceInstructionPolicyOnboardingProposal> {
2907
+ return await this.requestJson<WorkspaceInstructionPolicyOnboardingProposal>(
2908
+ "POST",
2909
+ `/v1/workspaces/${workspaceId}/instruction-policies/onboarding-proposals`,
2910
+ request,
2911
+ );
2912
+ }
2913
+
2914
+ /** Import the stored legacy workspace override as an inactive charter draft. */
2915
+ async importLegacyWorkspaceInstructionPolicyDraft(
2916
+ workspaceId: string,
2917
+ request: ImportLegacyWorkspaceInstructionPolicyDraftRequest = {},
2918
+ ): Promise<WorkspaceInstructionPolicyRevision> {
2919
+ return await this.requestJson<WorkspaceInstructionPolicyRevision>(
2920
+ "POST",
2921
+ `/v1/workspaces/${workspaceId}/instruction-policies/import-legacy`,
2922
+ request,
2923
+ );
2924
+ }
2925
+
2926
+ async diffWorkspaceInstructionPolicyRevisions(
2927
+ workspaceId: string,
2928
+ request: WorkspaceInstructionPolicyDiffRequest,
2929
+ ): Promise<WorkspaceInstructionPolicyDiffResponse> {
2930
+ const params = new URLSearchParams({
2931
+ fromRevisionId: request.fromRevisionId,
2932
+ toRevisionId: request.toRevisionId,
2933
+ });
2934
+ return await this.requestJson<WorkspaceInstructionPolicyDiffResponse>(
2935
+ "GET",
2936
+ `/v1/workspaces/${workspaceId}/instruction-policies/diff?${params}`,
2937
+ );
2200
2938
  }
2201
2939
 
2202
2940
  async activateWorkspaceInstructionPolicyRevision(
@@ -2222,6 +2960,77 @@ export class OpenGeniClient {
2222
2960
  );
2223
2961
  }
2224
2962
 
2963
+ /** Read the current organization profile plus immutable revision and activation history. */
2964
+ async listCompanyProfile(
2965
+ workspaceId: string,
2966
+ options: CompanyProfileListOptions = {},
2967
+ ): Promise<CompanyProfileListResponse> {
2968
+ const params = new URLSearchParams();
2969
+ if (options.afterRevision !== undefined)
2970
+ params.set("afterRevision", String(options.afterRevision));
2971
+ if (options.limit !== undefined) params.set("limit", String(options.limit));
2972
+ const query = params.toString();
2973
+ return await this.requestJson<CompanyProfileListResponse>(
2974
+ "GET",
2975
+ `/v1/workspaces/${workspaceId}/company-profile${query ? `?${query}` : ""}`,
2976
+ );
2977
+ }
2978
+
2979
+ async getCompanyProfileRevision(
2980
+ workspaceId: string,
2981
+ revisionId: string,
2982
+ ): Promise<CompanyProfileRevision> {
2983
+ return await this.requestJson<CompanyProfileRevision>(
2984
+ "GET",
2985
+ `/v1/workspaces/${workspaceId}/company-profile/revisions/${encodeURIComponent(revisionId)}`,
2986
+ );
2987
+ }
2988
+
2989
+ async updateCompanyProfile(
2990
+ workspaceId: string,
2991
+ request: UpdateCompanyProfileRequest,
2992
+ ): Promise<CompanyProfileMutationResponse> {
2993
+ return await this.requestJson<CompanyProfileMutationResponse>(
2994
+ "PUT",
2995
+ `/v1/workspaces/${workspaceId}/company-profile`,
2996
+ request,
2997
+ );
2998
+ }
2999
+
3000
+ async diffCompanyProfileRevisions(
3001
+ workspaceId: string,
3002
+ request: CompanyProfileDiffRequest,
3003
+ ): Promise<CompanyProfileDiffResponse> {
3004
+ const params = new URLSearchParams(request);
3005
+ return await this.requestJson<CompanyProfileDiffResponse>(
3006
+ "GET",
3007
+ `/v1/workspaces/${workspaceId}/company-profile/diff?${params}`,
3008
+ );
3009
+ }
3010
+
3011
+ async activateCompanyProfileRevision(
3012
+ workspaceId: string,
3013
+ revisionId: string,
3014
+ request: ActivateCompanyProfileRevisionRequest,
3015
+ ): Promise<CompanyProfileMutationResponse> {
3016
+ return await this.requestJson<CompanyProfileMutationResponse>(
3017
+ "POST",
3018
+ `/v1/workspaces/${workspaceId}/company-profile/revisions/${encodeURIComponent(revisionId)}/activate`,
3019
+ request,
3020
+ );
3021
+ }
3022
+
3023
+ async rollbackCompanyProfile(
3024
+ workspaceId: string,
3025
+ request: RollbackCompanyProfileRequest,
3026
+ ): Promise<CompanyProfileMutationResponse> {
3027
+ return await this.requestJson<CompanyProfileMutationResponse>(
3028
+ "POST",
3029
+ `/v1/workspaces/${workspaceId}/company-profile/rollback`,
3030
+ request,
3031
+ );
3032
+ }
3033
+
2225
3034
  async listPreferenceRegistry(
2226
3035
  workspaceId: string,
2227
3036
  options: PreferenceRegistryListOptions = {},
@@ -2405,6 +3214,86 @@ export class OpenGeniClient {
2405
3214
  );
2406
3215
  }
2407
3216
 
3217
+ /** Exchange one signed Slack bearer for durable, token-free continuation state. */
3218
+ async prepareSlackUserLinkAccess(
3219
+ workspaceId: string,
3220
+ request: PrepareSlackUserLinkAccessRequest,
3221
+ ): Promise<SlackUserLinkAccessRequest> {
3222
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3223
+ "POST",
3224
+ `/v1/workspaces/${workspaceId}/integrations/slack/user-link-intents`,
3225
+ request,
3226
+ );
3227
+ }
3228
+
3229
+ async getSlackUserLinkAccess(
3230
+ workspaceId: string,
3231
+ requestId: string,
3232
+ ): Promise<SlackUserLinkAccessRequest> {
3233
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3234
+ "GET",
3235
+ `/v1/workspaces/${workspaceId}/integrations/slack/user-link-intents/${requestId}`,
3236
+ );
3237
+ }
3238
+
3239
+ async requestSlackUserLinkWorkspaceAccess(
3240
+ workspaceId: string,
3241
+ requestId: string,
3242
+ request: SlackUserLinkAccessMutationRequest,
3243
+ ): Promise<SlackUserLinkAccessRequest> {
3244
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3245
+ "POST",
3246
+ `/v1/workspaces/${workspaceId}/integrations/slack/user-link-intents/${requestId}/request-access`,
3247
+ request,
3248
+ );
3249
+ }
3250
+
3251
+ async cancelSlackUserLinkAccess(
3252
+ workspaceId: string,
3253
+ requestId: string,
3254
+ request: SlackUserLinkAccessMutationRequest,
3255
+ ): Promise<SlackUserLinkAccessRequest> {
3256
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3257
+ "POST",
3258
+ `/v1/workspaces/${workspaceId}/integrations/slack/user-link-intents/${requestId}/cancel`,
3259
+ request,
3260
+ );
3261
+ }
3262
+
3263
+ async listSlackUserLinkAccessRequests(
3264
+ workspaceId: string,
3265
+ ): Promise<SlackUserLinkAccessRequest[]> {
3266
+ const response = await this.requestJson<ListSlackUserLinkAccessRequestsResponse>(
3267
+ "GET",
3268
+ `/v1/workspaces/${workspaceId}/members/access-requests/slack`,
3269
+ );
3270
+ return response.requests;
3271
+ }
3272
+
3273
+ async approveSlackUserLinkAccessRequest(
3274
+ workspaceId: string,
3275
+ requestId: string,
3276
+ request: ApproveSlackUserLinkAccessRequest,
3277
+ ): Promise<SlackUserLinkAccessRequest> {
3278
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3279
+ "POST",
3280
+ `/v1/workspaces/${workspaceId}/members/access-requests/slack/${requestId}/approve`,
3281
+ request,
3282
+ );
3283
+ }
3284
+
3285
+ async denySlackUserLinkAccessRequest(
3286
+ workspaceId: string,
3287
+ requestId: string,
3288
+ request: SlackUserLinkAccessMutationRequest,
3289
+ ): Promise<SlackUserLinkAccessRequest> {
3290
+ return await this.requestJson<SlackUserLinkAccessRequest>(
3291
+ "POST",
3292
+ `/v1/workspaces/${workspaceId}/members/access-requests/slack/${requestId}/deny`,
3293
+ request,
3294
+ );
3295
+ }
3296
+
2408
3297
  // --- Scheduled tasks (write + runs) -------------------------------------------
2409
3298
 
2410
3299
  async createScheduledTask(
@@ -2484,7 +3373,8 @@ export class OpenGeniClient {
2484
3373
  }
2485
3374
 
2486
3375
  // --- VariableSets --------------------------------------------------------------
2487
- // Variable values are write-only: reads return name/version metadata only.
3376
+ // Generic reads return name/version metadata only. Plaintext uses one
3377
+ // dedicated permissioned endpoint.
2488
3378
 
2489
3379
  async listVariableSets(workspaceId: string): Promise<VariableSet[]> {
2490
3380
  return await this.requestJson<VariableSet[]>(
@@ -2511,6 +3401,17 @@ export class OpenGeniClient {
2511
3401
  );
2512
3402
  }
2513
3403
 
3404
+ async getVariableSetVariable(
3405
+ workspaceId: string,
3406
+ variableSetId: string,
3407
+ name: string,
3408
+ ): Promise<VariableSetSecret> {
3409
+ return await this.requestJson<VariableSetSecret>(
3410
+ "GET",
3411
+ `/v1/workspaces/${workspaceId}/variable-sets/${variableSetId}/variables/${encodeURIComponent(name)}`,
3412
+ );
3413
+ }
3414
+
2514
3415
  async updateVariableSet(
2515
3416
  workspaceId: string,
2516
3417
  variableSetId: string,
@@ -2530,7 +3431,7 @@ export class OpenGeniClient {
2530
3431
  );
2531
3432
  }
2532
3433
 
2533
- /** Create or rotate a variable. The value never comes back on any read. */
3434
+ /** Create or rotate a variable. Generic reads never return its value. */
2534
3435
  async setVariableSetVariable(
2535
3436
  workspaceId: string,
2536
3437
  variableSetId: string,
@@ -2726,19 +3627,29 @@ export class OpenGeniClient {
2726
3627
  async beginFileUpload(
2727
3628
  workspaceId: string,
2728
3629
  request: CreateFileUploadRequest,
3630
+ options: OpenGeniRequestOptions = {},
2729
3631
  ): Promise<CreateFileUploadResponse> {
2730
3632
  return await this.requestJson<CreateFileUploadResponse>(
2731
3633
  "POST",
2732
3634
  `/v1/workspaces/${workspaceId}/files/uploads`,
2733
3635
  request,
3636
+ {},
3637
+ options,
2734
3638
  );
2735
3639
  }
2736
3640
 
2737
3641
  /** Step 3 of the upload flow: server verifies the object and marks it ready. */
2738
- async completeFileUpload(workspaceId: string, uploadId: string): Promise<FileAsset> {
3642
+ async completeFileUpload(
3643
+ workspaceId: string,
3644
+ uploadId: string,
3645
+ options: OpenGeniRequestOptions = {},
3646
+ ): Promise<FileAsset> {
2739
3647
  const response = await this.requestJson<CompleteFileUploadResponse>(
2740
3648
  "POST",
2741
3649
  `/v1/workspaces/${workspaceId}/files/uploads/${uploadId}/complete`,
3650
+ undefined,
3651
+ {},
3652
+ options,
2742
3653
  );
2743
3654
  return response.file;
2744
3655
  }
@@ -2749,6 +3660,30 @@ export class OpenGeniClient {
2749
3660
  * -> complete. Returns the ready `FileAsset`.
2750
3661
  */
2751
3662
  async uploadFile(workspaceId: string, input: UploadFileInput): Promise<FileAsset> {
3663
+ if (
3664
+ input.timeoutMs !== undefined &&
3665
+ (!Number.isFinite(input.timeoutMs) || input.timeoutMs <= 0)
3666
+ ) {
3667
+ throw new Error("File upload timeout must be a positive number");
3668
+ }
3669
+ const withTimeout = async <T>(
3670
+ timeoutMs: number,
3671
+ operation: (signal: AbortSignal) => Promise<T>,
3672
+ ): Promise<T> => {
3673
+ const controller = new AbortController();
3674
+ let timeout: ReturnType<typeof setTimeout> | undefined;
3675
+ const timedOut = new Promise<never>((_resolve, reject) => {
3676
+ timeout = setTimeout(() => {
3677
+ controller.abort();
3678
+ reject(new Error("File upload timed out. Retry the upload."));
3679
+ }, timeoutMs);
3680
+ });
3681
+ try {
3682
+ return await Promise.race([operation(controller.signal), timedOut]);
3683
+ } finally {
3684
+ if (timeout !== undefined) clearTimeout(timeout);
3685
+ }
3686
+ };
2752
3687
  // Snapshot mutable inputs before hashing so the digest always describes the
2753
3688
  // exact bytes later sent to object storage. Copy Uint8Array views into a
2754
3689
  // Blob so byte offsets/shared buffers can't leak surrounding bytes.
@@ -2765,32 +3700,52 @@ export class OpenGeniClient {
2765
3700
  ? body.size
2766
3701
  : body.byteLength;
2767
3702
  const sha256 = input.sha256 ?? (await sha256ForUpload(body));
2768
- const upload = await this.beginFileUpload(workspaceId, {
2769
- filename: input.filename,
2770
- contentType: input.contentType,
2771
- sizeBytes,
2772
- sha256,
2773
- });
2774
- const putResponse = await this.fetchImpl(upload.putUrl, {
2775
- method: "PUT",
2776
- // Signed object-storage URLs carry their own short-lived authority.
2777
- // Browser cookies and HTTP auth must never accompany this cross-origin
2778
- // request: credentialed fetches are incompatible with wildcard CORS and
2779
- // can leak ambient credentials to a caller-selected storage endpoint.
2780
- credentials: "omit",
2781
- // The backend's requiredHeaders already carry the canonical lowercase
2782
- // `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
2783
- // a `Content-Type` key here: WHATWG Headers treats the two casings as the
2784
- // same header and comma-joins their values (e.g. "text/plain, text/plain"),
2785
- // which the object store persists verbatim and COMPLETE then rejects (422),
2786
- // and which breaks S3's presigned-URL signature.
2787
- headers: { ...upload.requiredHeaders },
2788
- body,
2789
- });
3703
+ const upload = await withTimeout(
3704
+ 30_000,
3705
+ async (signal) =>
3706
+ await this.beginFileUpload(
3707
+ workspaceId,
3708
+ {
3709
+ filename: input.filename,
3710
+ contentType: input.contentType,
3711
+ sizeBytes,
3712
+ sha256,
3713
+ },
3714
+ { signal },
3715
+ ),
3716
+ );
3717
+ // Give large valid uploads enough time at a conservative 256 KiB/s while
3718
+ // still bounding an object-storage request that never settles.
3719
+ const transferTimeoutMs =
3720
+ input.timeoutMs ?? Math.max(120_000, Math.ceil(sizeBytes / (256 * 1024)) * 1_000);
3721
+ const putResponse = await withTimeout(
3722
+ transferTimeoutMs,
3723
+ async (signal) =>
3724
+ await this.fetchImpl(upload.putUrl, {
3725
+ method: "PUT",
3726
+ // Signed object-storage URLs carry their own short-lived authority.
3727
+ // Browser cookies and HTTP auth must never accompany this cross-origin
3728
+ // request: credentialed fetches are incompatible with wildcard CORS and
3729
+ // can leak ambient credentials to a caller-selected storage endpoint.
3730
+ credentials: "omit",
3731
+ // The backend's requiredHeaders already carry the canonical lowercase
3732
+ // `content-type` for every storage backend (Azure/S3/GCS). Do NOT also set
3733
+ // a `Content-Type` key here: WHATWG Headers treats the two casings as the
3734
+ // same header and comma-joins their values (e.g. "text/plain, text/plain"),
3735
+ // which the object store persists verbatim and COMPLETE then rejects (422),
3736
+ // and which breaks S3's presigned-URL signature.
3737
+ headers: { ...upload.requiredHeaders },
3738
+ body,
3739
+ signal,
3740
+ }),
3741
+ );
2790
3742
  if (!putResponse.ok) {
2791
3743
  throw await apiErrorFromResponse(putResponse, { method: "PUT" });
2792
3744
  }
2793
- return await this.completeFileUpload(workspaceId, upload.uploadId);
3745
+ return await withTimeout(
3746
+ 30_000,
3747
+ async (signal) => await this.completeFileUpload(workspaceId, upload.uploadId, { signal }),
3748
+ );
2794
3749
  }
2795
3750
 
2796
3751
  async getFile(workspaceId: string, fileId: string): Promise<FileAsset> {
@@ -2819,23 +3774,150 @@ export class OpenGeniClient {
2819
3774
  workspaceId: string,
2820
3775
  artifactId: string,
2821
3776
  options: RetainedArtifactContentOptions = {},
3777
+ ): Promise<RetainedArtifactContent> {
3778
+ return await this.getRetainedArtifactContentAtPath(
3779
+ `/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`,
3780
+ options,
3781
+ );
3782
+ }
3783
+
3784
+ async getSessionRetainedArtifact(
3785
+ workspaceId: string,
3786
+ sessionId: string,
3787
+ artifactId: string,
3788
+ ): Promise<RetainedArtifactMetadata> {
3789
+ return await this.requestJson<RetainedArtifactMetadata>(
3790
+ "GET",
3791
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}`,
3792
+ );
3793
+ }
3794
+
3795
+ async getSessionRetainedArtifactContent(
3796
+ workspaceId: string,
3797
+ sessionId: string,
3798
+ artifactId: string,
3799
+ options: RetainedArtifactContentOptions = {},
3800
+ ): Promise<RetainedArtifactContent> {
3801
+ return await this.getRetainedArtifactContentAtPath(
3802
+ `/v1/workspaces/${workspaceId}/sessions/${sessionId}/artifacts/${artifactId}/content`,
3803
+ options,
3804
+ );
3805
+ }
3806
+
3807
+ /** Assemble one permanent generated-image receipt from bounded authenticated ranges. */
3808
+ async downloadRetainedArtifact(
3809
+ workspaceId: string,
3810
+ artifact: RetainedArtifactReference,
3811
+ options: RetainedArtifactDownloadOptions = {},
3812
+ ): Promise<RetainedArtifactDownload> {
3813
+ assertRetainedGeneratedImageReceipt(workspaceId, artifact);
3814
+ const bytes = await this.downloadRetainedArtifactBytes(artifact, options);
3815
+ return { artifact, bytes };
3816
+ }
3817
+
3818
+ /** Mint a short-lived, zero-copy browser source for a validated generated image. */
3819
+ async createRetainedArtifactDownloadUrl(
3820
+ workspaceId: string,
3821
+ artifact: RetainedArtifactReference,
3822
+ options: OpenGeniRequestOptions = {},
3823
+ ): Promise<FileDownloadUrlResponse> {
3824
+ assertRetainedGeneratedImageReceipt(workspaceId, artifact);
3825
+ const download = await this.createFileDownloadUrl(workspaceId, artifact.artifactId, options);
3826
+ assertSafeArtifactDownloadUrl(download);
3827
+ return download;
3828
+ }
3829
+
3830
+ /** Assemble one retained screenshot from bounded authenticated API ranges. */
3831
+ async downloadRetainedScreenshot(
3832
+ workspaceId: string,
3833
+ sessionId: string,
3834
+ artifactId: string,
3835
+ options: RetainedScreenshotDownloadOptions = {},
3836
+ ): Promise<RetainedScreenshotDownload> {
3837
+ const metadata = await this.getSessionRetainedArtifact(workspaceId, sessionId, artifactId);
3838
+ if (!metadata.available) return { metadata, bytes: null };
3839
+ if (
3840
+ metadata.kind !== "computer_screenshot" ||
3841
+ metadata.contentType !== "image/png" ||
3842
+ !metadata.dimensions ||
3843
+ metadata.originalBytes <= 0 ||
3844
+ metadata.originalBytes > COMPUTER_SCREENSHOT_MAX_BYTES
3845
+ ) {
3846
+ throw new OpenGeniApiError(502, "retained screenshot metadata is invalid");
3847
+ }
3848
+ const bytes = await this.downloadRetainedArtifactBytes(metadata, options);
3849
+ return { metadata, bytes };
3850
+ }
3851
+
3852
+ private async downloadRetainedArtifactBytes(
3853
+ artifact: RetainedArtifactReference,
3854
+ options: RetainedArtifactDownloadOptions,
3855
+ ): Promise<Uint8Array> {
3856
+ const maxRetries = options.maxRetries ?? 2;
3857
+ if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 3) {
3858
+ throw new RangeError("retained artifact maxRetries must be an integer from 0 to 3");
3859
+ }
3860
+ const bytes = new Uint8Array(artifact.originalBytes);
3861
+ const pageBytes = Math.min(artifact.retrieval.maxRangeBytes, RETAINED_OUTPUT_MAX_PAGE_BYTES);
3862
+ if (!Number.isSafeInteger(pageBytes) || pageBytes <= 0) {
3863
+ throw new OpenGeniApiError(502, "retained artifact range metadata is invalid");
3864
+ }
3865
+ for (let start = 0; start < bytes.byteLength; start += pageBytes) {
3866
+ options.signal?.throwIfAborted();
3867
+ const end = Math.min(start + pageBytes, bytes.byteLength) - 1;
3868
+ let page: RetainedArtifactContent | null = null;
3869
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
3870
+ try {
3871
+ page = await this.getRetainedArtifactContentAtPath(artifact.retrieval.path, {
3872
+ range: `bytes=${start}-${end}`,
3873
+ ...(options.signal ? { signal: options.signal } : {}),
3874
+ });
3875
+ break;
3876
+ } catch (error) {
3877
+ options.signal?.throwIfAborted();
3878
+ if (
3879
+ attempt >= maxRetries ||
3880
+ (error instanceof OpenGeniApiError && error.status >= 400 && error.status < 500)
3881
+ ) {
3882
+ throw error;
3883
+ }
3884
+ }
3885
+ }
3886
+ if (!page) throw new OpenGeniApiError(502, "retained artifact range retry exhausted");
3887
+ const expectedLength = end - start + 1;
3888
+ if (
3889
+ page.status !== 206 ||
3890
+ page.contentType !== artifact.contentType ||
3891
+ page.contentLength !== expectedLength ||
3892
+ page.contentRange !== `bytes ${start}-${end}/${artifact.originalBytes}`
3893
+ ) {
3894
+ throw new OpenGeniApiError(502, "retained artifact range response is invalid");
3895
+ }
3896
+ bytes.set(page.bytes, start);
3897
+ }
3898
+ if ((await sha256Hex(bytes)) !== artifact.sha256) {
3899
+ throw new OpenGeniApiError(502, "retained artifact checksum mismatch");
3900
+ }
3901
+ return bytes;
3902
+ }
3903
+
3904
+ private async getRetainedArtifactContentAtPath(
3905
+ path: string,
3906
+ options: RetainedArtifactContentOptions,
2822
3907
  ): Promise<RetainedArtifactContent> {
2823
3908
  if (options.range && (options.range.length > 128 || /[^\x20-\x7e]/.test(options.range))) {
2824
3909
  throw new RangeError("retained artifact range must be at most 128 printable ASCII bytes");
2825
3910
  }
2826
3911
  const correlationId = crypto.randomUUID();
2827
- const response = await this.fetchImpl(
2828
- this.url(`/v1/workspaces/${workspaceId}/artifacts/${artifactId}/content`),
2829
- {
2830
- method: "GET",
2831
- headers: {
2832
- ...this.headers(correlationId),
2833
- Accept: "application/octet-stream",
2834
- ...(options.range ? { Range: options.range } : {}),
2835
- },
2836
- ...(options.signal ? { signal: options.signal } : {}),
3912
+ const response = await this.fetchImpl(this.url(path), {
3913
+ method: "GET",
3914
+ headers: {
3915
+ ...this.headers(correlationId),
3916
+ Accept: "application/octet-stream",
3917
+ ...(options.range ? { Range: options.range } : {}),
2837
3918
  },
2838
- );
3919
+ ...(options.signal ? { signal: options.signal } : {}),
3920
+ });
2839
3921
  try {
2840
3922
  assertApiContractResponse(response);
2841
3923
  } catch (error) {
@@ -2868,25 +3950,92 @@ export class OpenGeniClient {
2868
3950
  RETAINED_OUTPUT_MAX_PAGE_BYTES,
2869
3951
  declaredLength,
2870
3952
  );
2871
- return {
2872
- bytes,
2873
- status: response.status,
2874
- contentType: response.headers.get("content-type") ?? "application/octet-stream",
2875
- contentLength: bytes.byteLength,
2876
- contentRange: response.headers.get("content-range"),
2877
- acceptRanges: "bytes",
2878
- };
3953
+ return {
3954
+ bytes,
3955
+ status: response.status,
3956
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
3957
+ contentLength: bytes.byteLength,
3958
+ contentRange: response.headers.get("content-range"),
3959
+ acceptRanges: "bytes",
3960
+ };
3961
+ }
3962
+
3963
+ /** Mint a short-lived signed download URL for a ready file. */
3964
+ async createFileDownloadUrl(
3965
+ workspaceId: string,
3966
+ fileId: string,
3967
+ options: OpenGeniRequestOptions = {},
3968
+ ): Promise<FileDownloadUrlResponse> {
3969
+ return await this.requestJson<FileDownloadUrlResponse>(
3970
+ "POST",
3971
+ `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`,
3972
+ undefined,
3973
+ {},
3974
+ options,
3975
+ );
3976
+ }
3977
+
3978
+ // --- Video generation ---------------------------------------------------------------
3979
+
3980
+ /** Read the effective workspace policy and executable Seedance capabilities. */
3981
+ async getVideoGenerationSettings(
3982
+ workspaceId: string,
3983
+ options: OpenGeniRequestOptions = {},
3984
+ ): Promise<WorkspaceVideoGenerationSettings> {
3985
+ return await this.requestJson<WorkspaceVideoGenerationSettings>(
3986
+ "GET",
3987
+ `/v1/workspaces/${workspaceId}/video-generation`,
3988
+ undefined,
3989
+ {},
3990
+ options,
3991
+ );
3992
+ }
3993
+
3994
+ /** Replace the enabled video models/default under optimistic policy revision control. */
3995
+ async updateVideoGenerationPolicy(
3996
+ workspaceId: string,
3997
+ request: UpdateVideoGenerationPolicyRequest,
3998
+ options: OpenGeniRequestOptions = {},
3999
+ ): Promise<VideoGenerationPolicy> {
4000
+ return await this.requestJson<VideoGenerationPolicy>(
4001
+ "PUT",
4002
+ `/v1/workspaces/${workspaceId}/video-generation/policy`,
4003
+ request,
4004
+ {},
4005
+ options,
4006
+ );
2879
4007
  }
2880
4008
 
2881
- /** Mint a short-lived signed download URL for a ready file. */
2882
- async createFileDownloadUrl(
4009
+ /** Read durable progress for one accepted video generation operation. */
4010
+ async getVideoGenerationOperation(
2883
4011
  workspaceId: string,
2884
- fileId: string,
2885
- ): Promise<FileDownloadUrlResponse> {
2886
- return await this.requestJson<FileDownloadUrlResponse>(
4012
+ operationId: string,
4013
+ options: OpenGeniRequestOptions = {},
4014
+ ): Promise<VideoGenerationOperationSummary> {
4015
+ return await this.requestJson<VideoGenerationOperationSummary>(
4016
+ "GET",
4017
+ `/v1/workspaces/${workspaceId}/video-generation/operations/${operationId}`,
4018
+ undefined,
4019
+ {},
4020
+ options,
4021
+ );
4022
+ }
4023
+
4024
+ /** Mint a short-lived zero-copy URL for native Range-based video playback. */
4025
+ async createVideoArtifactPlaybackSource(
4026
+ workspaceId: string,
4027
+ artifactId: string,
4028
+ options: OpenGeniRequestOptions = {},
4029
+ ): Promise<VideoArtifactPlaybackSource> {
4030
+ const source = await this.requestJson<VideoArtifactPlaybackSource>(
2887
4031
  "POST",
2888
- `/v1/workspaces/${workspaceId}/files/${fileId}/download-url`,
4032
+ `/v1/workspaces/${workspaceId}/artifacts/${artifactId}/playback-source`,
4033
+ undefined,
4034
+ {},
4035
+ options,
2889
4036
  );
4037
+ assertSafeVideoArtifactPlaybackSource(source, artifactId);
4038
+ return source;
2890
4039
  }
2891
4040
 
2892
4041
  // --- Documents ----------------------------------------------------------------------
@@ -3135,6 +4284,53 @@ export class OpenGeniClient {
3135
4284
  );
3136
4285
  }
3137
4286
 
4287
+ /** Resolve pinned components, Variable Set, and Rig requirements before installation. */
4288
+ async previewPackInstallation(
4289
+ workspaceId: string,
4290
+ packId: string,
4291
+ request: PreviewPackInstallationRequest = {},
4292
+ ): Promise<PackInstallationPreview> {
4293
+ return await this.requestJson<PackInstallationPreview>(
4294
+ "POST",
4295
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/installation-preview`,
4296
+ request,
4297
+ );
4298
+ }
4299
+
4300
+ /** Install, update, or repair a Pack from an exact previewed manifest. */
4301
+ async installPack(
4302
+ workspaceId: string,
4303
+ packId: string,
4304
+ request: InstallPackRequest,
4305
+ ): Promise<PackInstallation> {
4306
+ return await this.requestJson<PackInstallation>(
4307
+ "POST",
4308
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/install`,
4309
+ request,
4310
+ );
4311
+ }
4312
+
4313
+ /** Preview which Pack-owned components will be retained by other owners. */
4314
+ async previewPackUninstall(workspaceId: string, packId: string): Promise<PackUninstallPreview> {
4315
+ return await this.requestJson<PackUninstallPreview>(
4316
+ "GET",
4317
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/uninstall-preview`,
4318
+ );
4319
+ }
4320
+
4321
+ /** Safely release Pack ownership and disable only now-ownerless components. */
4322
+ async uninstallPack(
4323
+ workspaceId: string,
4324
+ packId: string,
4325
+ request: UninstallPackRequest,
4326
+ ): Promise<UninstallPackResult> {
4327
+ return await this.requestJson<UninstallPackResult>(
4328
+ "DELETE",
4329
+ `/v1/workspaces/${workspaceId}/packs/${encodeURIComponent(packId)}/installation`,
4330
+ request,
4331
+ );
4332
+ }
4333
+
3138
4334
  /** Unregister a workspace-scoped pack (built-in packs cannot be deleted). */
3139
4335
  async deletePack(workspaceId: string, packId: string): Promise<void> {
3140
4336
  await this.requestVoid(
@@ -3209,6 +4405,298 @@ export class OpenGeniClient {
3209
4405
  );
3210
4406
  }
3211
4407
 
4408
+ /** List installed protocol-neutral OpenAPI and GraphQL Integrations. */
4409
+ async listApiIntegrations(workspaceId: string): Promise<ListApiIntegrationsResponse> {
4410
+ return await this.requestJson<ListApiIntegrationsResponse>(
4411
+ "GET",
4412
+ `/v1/workspaces/${workspaceId}/integrations`,
4413
+ );
4414
+ }
4415
+
4416
+ /** List curated provider presets without exposing deployment OAuth credentials. */
4417
+ async listApiIntegrationPresets(workspaceId: string): Promise<ListApiIntegrationPresetsResponse> {
4418
+ return await this.requestJson<ListApiIntegrationPresetsResponse>(
4419
+ "GET",
4420
+ `/v1/workspaces/${workspaceId}/integrations/presets`,
4421
+ );
4422
+ }
4423
+
4424
+ /** Detect and compile an Integration without mutating workspace state. */
4425
+ async previewApiIntegration(
4426
+ workspaceId: string,
4427
+ request: PreviewApiIntegrationRequest,
4428
+ ): Promise<ApiIntegrationPreview> {
4429
+ return await this.requestJson<ApiIntegrationPreview>(
4430
+ "POST",
4431
+ `/v1/workspaces/${workspaceId}/integrations/preview`,
4432
+ request,
4433
+ );
4434
+ }
4435
+
4436
+ /** Start a signed PKCE OAuth flow for a built-in Google or Microsoft preset. */
4437
+ async startApiIntegrationOAuth(
4438
+ workspaceId: string,
4439
+ request: ApiIntegrationOAuthStartRequest,
4440
+ ): Promise<OAuthStartResponse> {
4441
+ return await this.requestJson<OAuthStartResponse>(
4442
+ "POST",
4443
+ `/v1/workspaces/${workspaceId}/integrations/oauth/start`,
4444
+ request,
4445
+ );
4446
+ }
4447
+
4448
+ /** Install only the exact immutable revision and digest accepted in preview. */
4449
+ async installApiIntegration(
4450
+ workspaceId: string,
4451
+ request: InstallApiIntegrationRequest,
4452
+ ): Promise<InstalledApiIntegration> {
4453
+ return await this.requestJson<InstalledApiIntegration>(
4454
+ "POST",
4455
+ `/v1/workspaces/${workspaceId}/integrations/install`,
4456
+ request,
4457
+ );
4458
+ }
4459
+
4460
+ async previewApiIntegrationUninstall(
4461
+ workspaceId: string,
4462
+ capabilityId: string,
4463
+ instanceKey: string,
4464
+ ): Promise<ApiIntegrationUninstallPreview> {
4465
+ return await this.requestJson<ApiIntegrationUninstallPreview>(
4466
+ "GET",
4467
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/uninstall-preview`,
4468
+ );
4469
+ }
4470
+
4471
+ /** Remove one Integration instance without deleting its Connection or sibling instances. */
4472
+ async uninstallApiIntegration(
4473
+ workspaceId: string,
4474
+ capabilityId: string,
4475
+ instanceKey: string,
4476
+ request: UninstallApiIntegrationRequest,
4477
+ ): Promise<UninstallApiIntegrationResult> {
4478
+ return await this.requestJson<UninstallApiIntegrationResult>(
4479
+ "DELETE",
4480
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}`,
4481
+ request,
4482
+ );
4483
+ }
4484
+
4485
+ /** List immutable provider facets and their exact per-account bindings. */
4486
+ async listIntegrationFeatures(
4487
+ workspaceId: string,
4488
+ capabilityId: string,
4489
+ instanceKey: string,
4490
+ ): Promise<IntegrationInstanceFeaturesResponse> {
4491
+ return await this.requestJson<IntegrationInstanceFeaturesResponse>(
4492
+ "GET",
4493
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features`,
4494
+ );
4495
+ }
4496
+
4497
+ /** Configure or OCC-update one adapter-owned feature on an Integration instance. */
4498
+ async configureIntegrationFeature(
4499
+ workspaceId: string,
4500
+ capabilityId: string,
4501
+ instanceKey: string,
4502
+ featureKey: string,
4503
+ request: UpsertIntegrationFeatureRequest,
4504
+ ): Promise<IntegrationFeatureMutationResult> {
4505
+ return await this.requestJson<IntegrationFeatureMutationResult>(
4506
+ "PUT",
4507
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features/${encodeURIComponent(featureKey)}`,
4508
+ request,
4509
+ );
4510
+ }
4511
+
4512
+ /** Browse source metadata through one exact Google Drive Integration instance. */
4513
+ async browseGoogleDriveIntegrationSource(
4514
+ workspaceId: string,
4515
+ capabilityId: string,
4516
+ instanceKey: string,
4517
+ featureKey: string,
4518
+ options: { parentId?: string | undefined; pageToken?: string | undefined } = {},
4519
+ ): Promise<GoogleDriveBrowseResponse> {
4520
+ const query = new URLSearchParams();
4521
+ if (options.parentId) query.set("parentId", options.parentId);
4522
+ if (options.pageToken) query.set("pageToken", options.pageToken);
4523
+ const suffix = query.size > 0 ? `?${query}` : "";
4524
+ return await this.requestJson<GoogleDriveBrowseResponse>(
4525
+ "GET",
4526
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features/${encodeURIComponent(featureKey)}/browse${suffix}`,
4527
+ );
4528
+ }
4529
+
4530
+ /** Verify and bind document sources to one exact Google Drive Integration instance. */
4531
+ async saveGoogleDriveIntegrationSource(
4532
+ workspaceId: string,
4533
+ capabilityId: string,
4534
+ instanceKey: string,
4535
+ featureKey: string,
4536
+ request: SaveGoogleDriveIntegrationSourceRequest,
4537
+ ): Promise<IntegrationFeatureMutationResult> {
4538
+ return await this.requestJson<IntegrationFeatureMutationResult>(
4539
+ "PUT",
4540
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features/${encodeURIComponent(featureKey)}/source`,
4541
+ request,
4542
+ );
4543
+ }
4544
+
4545
+ async pauseIntegrationFeature(
4546
+ workspaceId: string,
4547
+ capabilityId: string,
4548
+ instanceKey: string,
4549
+ featureKey: string,
4550
+ request: MutateIntegrationFeatureRequest,
4551
+ ): Promise<IntegrationFeatureMutationResult> {
4552
+ return await this.mutateIntegrationFeatureLifecycle(
4553
+ workspaceId,
4554
+ capabilityId,
4555
+ instanceKey,
4556
+ featureKey,
4557
+ "pause",
4558
+ request,
4559
+ );
4560
+ }
4561
+
4562
+ async resumeIntegrationFeature(
4563
+ workspaceId: string,
4564
+ capabilityId: string,
4565
+ instanceKey: string,
4566
+ featureKey: string,
4567
+ request: MutateIntegrationFeatureRequest,
4568
+ ): Promise<IntegrationFeatureMutationResult> {
4569
+ return await this.mutateIntegrationFeatureLifecycle(
4570
+ workspaceId,
4571
+ capabilityId,
4572
+ instanceKey,
4573
+ featureKey,
4574
+ "resume",
4575
+ request,
4576
+ );
4577
+ }
4578
+
4579
+ async removeIntegrationFeature(
4580
+ workspaceId: string,
4581
+ capabilityId: string,
4582
+ instanceKey: string,
4583
+ featureKey: string,
4584
+ request: MutateIntegrationFeatureRequest,
4585
+ ): Promise<IntegrationFeatureRemovalResult> {
4586
+ return await this.requestJson<IntegrationFeatureRemovalResult>(
4587
+ "DELETE",
4588
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features/${encodeURIComponent(featureKey)}`,
4589
+ request,
4590
+ );
4591
+ }
4592
+
4593
+ private async mutateIntegrationFeatureLifecycle(
4594
+ workspaceId: string,
4595
+ capabilityId: string,
4596
+ instanceKey: string,
4597
+ featureKey: string,
4598
+ action: "pause" | "resume",
4599
+ request: MutateIntegrationFeatureRequest,
4600
+ ): Promise<IntegrationFeatureMutationResult> {
4601
+ return await this.requestJson<IntegrationFeatureMutationResult>(
4602
+ "POST",
4603
+ `/v1/workspaces/${workspaceId}/integrations/${encodeURIComponent(capabilityId)}/instances/${encodeURIComponent(instanceKey)}/features/${encodeURIComponent(featureKey)}/${action}`,
4604
+ request,
4605
+ );
4606
+ }
4607
+
4608
+ async previewPlugin(workspaceId: string, request: PreviewPluginRequest): Promise<PluginPreview> {
4609
+ return await this.requestJson<PluginPreview>(
4610
+ "POST",
4611
+ `/v1/workspaces/${workspaceId}/plugins/preview`,
4612
+ request,
4613
+ );
4614
+ }
4615
+
4616
+ async listInstalledPlugins(workspaceId: string): Promise<ListInstalledPluginsResponse> {
4617
+ return await this.requestJson<ListInstalledPluginsResponse>(
4618
+ "GET",
4619
+ `/v1/workspaces/${workspaceId}/plugins`,
4620
+ );
4621
+ }
4622
+
4623
+ async installPlugin(
4624
+ workspaceId: string,
4625
+ request: InstallPluginRequest,
4626
+ ): Promise<InstalledPlugin> {
4627
+ return await this.requestJson<InstalledPlugin>(
4628
+ "POST",
4629
+ `/v1/workspaces/${workspaceId}/plugins/install`,
4630
+ request,
4631
+ );
4632
+ }
4633
+
4634
+ async previewPluginUninstall(
4635
+ workspaceId: string,
4636
+ pluginKey: string,
4637
+ ): Promise<PluginUninstallPreview> {
4638
+ return await this.requestJson<PluginUninstallPreview>(
4639
+ "GET",
4640
+ `/v1/workspaces/${workspaceId}/plugins/${encodeURIComponent(pluginKey)}/uninstall-preview`,
4641
+ );
4642
+ }
4643
+
4644
+ async uninstallPlugin(
4645
+ workspaceId: string,
4646
+ pluginKey: string,
4647
+ request: UninstallPluginRequest,
4648
+ ): Promise<UninstallPluginResult> {
4649
+ return await this.requestJson<UninstallPluginResult>(
4650
+ "DELETE",
4651
+ `/v1/workspaces/${workspaceId}/plugins/${encodeURIComponent(pluginKey)}`,
4652
+ request,
4653
+ );
4654
+ }
4655
+
4656
+ /** Resolve one public skills.sh or GitHub Skill folder to an immutable review preview. */
4657
+ async previewSkillImport(
4658
+ workspaceId: string,
4659
+ request: PreviewSkillImportRequest,
4660
+ ): Promise<SkillImportPreview> {
4661
+ return await this.requestJson<SkillImportPreview>(
4662
+ "POST",
4663
+ `/v1/workspaces/${workspaceId}/skills/preview`,
4664
+ request,
4665
+ );
4666
+ }
4667
+
4668
+ /** Install only the exact commit and full-content digest accepted during preview. */
4669
+ async installSkill(workspaceId: string, request: InstallSkillRequest): Promise<InstalledSkill> {
4670
+ return await this.requestJson<InstalledSkill>(
4671
+ "POST",
4672
+ `/v1/workspaces/${workspaceId}/skills/install`,
4673
+ request,
4674
+ );
4675
+ }
4676
+
4677
+ async previewSkillUninstall(
4678
+ workspaceId: string,
4679
+ capabilityId: string,
4680
+ ): Promise<SkillUninstallPreview> {
4681
+ return await this.requestJson<SkillUninstallPreview>(
4682
+ "GET",
4683
+ `/v1/workspaces/${workspaceId}/skills/${encodeURIComponent(capabilityId)}/uninstall-preview`,
4684
+ );
4685
+ }
4686
+
4687
+ /** Remove the direct owner under an optimistic installation-version fence. */
4688
+ async uninstallSkill(
4689
+ workspaceId: string,
4690
+ capabilityId: string,
4691
+ request: UninstallSkillRequest,
4692
+ ): Promise<UninstallSkillResult> {
4693
+ return await this.requestJson<UninstallSkillResult>(
4694
+ "DELETE",
4695
+ `/v1/workspaces/${workspaceId}/skills/${encodeURIComponent(capabilityId)}`,
4696
+ request,
4697
+ );
4698
+ }
4699
+
3212
4700
  // --- Connections -------------------------------------------------------------------------------
3213
4701
 
3214
4702
  async listConnections(workspaceId: string): Promise<ConnectionMetadata[]> {
@@ -3219,6 +4707,15 @@ export class OpenGeniClient {
3219
4707
  return response.connections;
3220
4708
  }
3221
4709
 
4710
+ /** List the secret-free Slack team -> OpenGeni tenant routing authority. */
4711
+ async listSlackInstallationBindings(workspaceId: string): Promise<SlackInstallationBinding[]> {
4712
+ const response = await this.requestJson<ListSlackInstallationBindingsResponse>(
4713
+ "GET",
4714
+ `/v1/workspaces/${workspaceId}/connections/slack-bot/bindings`,
4715
+ );
4716
+ return response.bindings;
4717
+ }
4718
+
3222
4719
  async createConnection(
3223
4720
  workspaceId: string,
3224
4721
  request: CreateConnectionRequest,
@@ -3361,8 +4858,16 @@ export class OpenGeniClient {
3361
4858
  // --- GitHub ----------------------------------------------------------------------------------
3362
4859
 
3363
4860
  /** GitHub App server configuration plus truthful workspace binding status. */
3364
- async getGitHubApp(workspaceId: string): Promise<GitHubAppInfo> {
3365
- return await this.requestJson<GitHubAppInfo>("GET", `/v1/workspaces/${workspaceId}/github/app`);
4861
+ async getGitHubApp(
4862
+ workspaceId: string,
4863
+ options: { returnPath?: string } = {},
4864
+ ): Promise<GitHubAppInfo> {
4865
+ return await this.requestJson<GitHubAppInfo>(
4866
+ "GET",
4867
+ `/v1/workspaces/${workspaceId}/github/app`,
4868
+ undefined,
4869
+ options.returnPath ? { returnPath: options.returnPath } : undefined,
4870
+ );
3366
4871
  }
3367
4872
 
3368
4873
  /** Build the GitHub owner-consent entry URL for fresh workspace-bound state. */
@@ -3682,7 +5187,8 @@ export class OpenGeniClient {
3682
5187
  );
3683
5188
  }
3684
5189
 
3685
- protected async requestJson<T>(
5190
+ /** Contract-checked JSON transport shared by opt-in typed SDK clients. */
5191
+ async requestJson<T>(
3686
5192
  method: string,
3687
5193
  path: string,
3688
5194
  body?: unknown,
@@ -3723,6 +5229,35 @@ export class OpenGeniClient {
3723
5229
  }
3724
5230
  }
3725
5231
 
5232
+ /** Authenticated, contract-checked response for bounded streaming downloads. */
5233
+ protected async requestResponse(
5234
+ method: string,
5235
+ path: string,
5236
+ query: Record<string, string> = {},
5237
+ options: OpenGeniRequestOptions = {},
5238
+ ): Promise<Response> {
5239
+ const correlationId = crypto.randomUUID();
5240
+ let response: Response;
5241
+ try {
5242
+ response = await this.fetchImpl(this.url(path, query), {
5243
+ method,
5244
+ headers: {
5245
+ ...this.headers(correlationId),
5246
+ Accept: "application/octet-stream",
5247
+ },
5248
+ ...(options.signal ? { signal: options.signal } : {}),
5249
+ });
5250
+ } catch (error) {
5251
+ if (isMutationMethod(method)) throw mutationTransportError(correlationId);
5252
+ throw error;
5253
+ }
5254
+ assertApiContractResponse(response);
5255
+ if (!response.ok) {
5256
+ throw await apiErrorFromResponse(response, { method, correlationId });
5257
+ }
5258
+ return response;
5259
+ }
5260
+
3726
5261
  /** Like `requestJson` for endpoints that respond with no body (204). */
3727
5262
  private async requestVoid(method: string, path: string, body?: unknown): Promise<void> {
3728
5263
  const correlationId = crypto.randomUUID();
@@ -3772,7 +5307,12 @@ function isUploadTranscriptionRecordingChunkResponse(
3772
5307
  ): value is UploadTranscriptionRecordingChunkResponse {
3773
5308
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3774
5309
  const record = value as Record<string, unknown>;
3775
- if (!isTranscriptionRecordingResponse({ recording: record.recording, segments: [] })) {
5310
+ if (
5311
+ !isTranscriptionRecordingResponse({
5312
+ recording: record.recording,
5313
+ segments: [],
5314
+ })
5315
+ ) {
3776
5316
  return false;
3777
5317
  }
3778
5318
  if (!record.chunk || typeof record.chunk !== "object" || Array.isArray(record.chunk)) {
@@ -3947,10 +5487,65 @@ async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<strin
3947
5487
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
3948
5488
  }
3949
5489
 
5490
+ async function sha256Hex(bytes: Uint8Array): Promise<string> {
5491
+ const owned = Uint8Array.from(bytes);
5492
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", owned.buffer);
5493
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
5494
+ }
5495
+
3950
5496
  async function cancelResponseBody(response: Response, reason: string): Promise<void> {
3951
5497
  await response.body?.cancel(reason).catch(() => undefined);
3952
5498
  }
3953
5499
 
5500
+ function assertRetainedGeneratedImageReceipt(
5501
+ workspaceId: string,
5502
+ artifact: RetainedArtifactReference,
5503
+ ): void {
5504
+ if (!parseRetainedGeneratedImageReference(artifact, workspaceId)) {
5505
+ throw new OpenGeniApiError(502, "retained generated image receipt is invalid");
5506
+ }
5507
+ }
5508
+
5509
+ function assertSafeArtifactDownloadUrl(value: FileDownloadUrlResponse): void {
5510
+ if (
5511
+ !value ||
5512
+ typeof value.url !== "string" ||
5513
+ typeof value.expiresAt !== "string" ||
5514
+ !Number.isFinite(Date.parse(value.expiresAt))
5515
+ ) {
5516
+ throw new OpenGeniApiError(502, "retained artifact download URL is invalid");
5517
+ }
5518
+ let parsed: URL;
5519
+ try {
5520
+ parsed = new URL(value.url);
5521
+ } catch {
5522
+ throw new OpenGeniApiError(502, "retained artifact download URL is invalid");
5523
+ }
5524
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) {
5525
+ throw new OpenGeniApiError(502, "retained artifact download URL is unsafe");
5526
+ }
5527
+ }
5528
+
5529
+ function assertSafeVideoArtifactPlaybackSource(
5530
+ value: VideoArtifactPlaybackSource,
5531
+ expectedArtifactId: string,
5532
+ ): void {
5533
+ if (
5534
+ !value ||
5535
+ value.schemaVersion !== 1 ||
5536
+ value.artifactId !== expectedArtifactId ||
5537
+ value.contentType !== "video/mp4" ||
5538
+ value.acceptRanges !== "bytes" ||
5539
+ !Number.isSafeInteger(value.sizeBytes) ||
5540
+ value.sizeBytes <= 0 ||
5541
+ value.sizeBytes > GENERATED_VIDEO_MAX_BYTES ||
5542
+ !/^[0-9a-f]{64}$/.test(value.sha256)
5543
+ ) {
5544
+ throw new OpenGeniApiError(502, "generated-video playback source is invalid");
5545
+ }
5546
+ assertSafeArtifactDownloadUrl({ url: value.url, expiresAt: value.expiresAt });
5547
+ }
5548
+
3954
5549
  function parseBoundedContentLength(value: string | null): number | null {
3955
5550
  if (value === null) return null;
3956
5551
  if (!/^\d+$/.test(value)) {