@opengeni/sdk 3.1.0 → 3.3.2-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +60 -8
  2. package/dist/accounts.d.ts +5 -1
  3. package/dist/accounts.js +13 -1
  4. package/dist/accounts.js.map +1 -1
  5. package/dist/artifacts.js +5 -5
  6. package/dist/browser.js +3 -3
  7. package/dist/{chunk-YTAWBDO2.js → chunk-JUS2UYRP.js} +2 -1
  8. package/dist/chunk-JUS2UYRP.js.map +1 -0
  9. package/dist/{chunk-GW3EJ2GP.js → chunk-KKRO2VCP.js} +27 -2
  10. package/dist/{chunk-GW3EJ2GP.js.map → chunk-KKRO2VCP.js.map} +1 -1
  11. package/dist/{chunk-VUQZAB3O.js → chunk-QT3KFVDP.js} +2 -2
  12. package/dist/{chunk-7ZXBPJZP.js → chunk-TRNI7KEO.js} +213 -42
  13. package/dist/chunk-TRNI7KEO.js.map +1 -0
  14. package/dist/{chunk-TX3EIENU.js → chunk-YXQX7RQT.js} +2 -2
  15. package/dist/client.d.ts +35 -11
  16. package/dist/company-profile.d.ts +13 -0
  17. package/dist/core.js +4 -4
  18. package/dist/document-authority.js +4 -4
  19. package/dist/editable-artifacts.js +1 -1
  20. package/dist/index.d.ts +4 -2
  21. package/dist/index.js +215 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/interaction.d.ts +3 -0
  24. package/dist/interaction.js +3 -1
  25. package/dist/realtime.d.ts +3 -1
  26. package/dist/realtime.js.map +1 -1
  27. package/dist/session-titles.d.ts +28 -0
  28. package/dist/types.d.ts +58 -2
  29. package/package.json +2 -2
  30. package/src/accounts.ts +18 -0
  31. package/src/client.ts +291 -35
  32. package/src/company-profile.ts +13 -0
  33. package/src/index.ts +18 -0
  34. package/src/interaction.ts +32 -0
  35. package/src/realtime.ts +1 -0
  36. package/src/session-titles.ts +84 -0
  37. package/src/types.ts +64 -1
  38. package/dist/chunk-7ZXBPJZP.js.map +0 -1
  39. package/dist/chunk-YTAWBDO2.js.map +0 -1
  40. /package/dist/{chunk-VUQZAB3O.js.map → chunk-QT3KFVDP.js.map} +0 -0
  41. /package/dist/{chunk-TX3EIENU.js.map → chunk-YXQX7RQT.js.map} +0 -0
package/src/client.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  } from "./workspace-live-stream";
24
24
  import {
25
25
  OpenGeniInteractionClient,
26
+ decodeComputerFrameMetadataHeader,
26
27
  type AuthRun,
27
28
  type AuthRunListOptions,
28
29
  type AuthRunListResponse,
@@ -57,6 +58,7 @@ import {
57
58
  type ComputerActionReceipt,
58
59
  type ComputerActionRequest,
59
60
  type ComputerClipboard,
61
+ type ComputerFrame,
60
62
  type ComputerObservation,
61
63
  type ComputerSession,
62
64
  type ComputerSessionAttachment,
@@ -184,6 +186,7 @@ import type {
184
186
  VerifyPersonalGitHubRepositorySelectionsRequest,
185
187
  CreateApiKeyRequest,
186
188
  CreateApiKeyResponse,
189
+ CreateOrganizationApiKeyRequest,
187
190
  CreateCapabilityCatalogItemRequest,
188
191
  InstallSkillRequest,
189
192
  InstallLibrarySkillRequest,
@@ -216,6 +219,8 @@ import type {
216
219
  ResolveVariableSetAttachmentsResponse,
217
220
  CreateRigRequest,
218
221
  CreateWorkspaceRequest,
222
+ EnsureWorkspaceRequest,
223
+ EnsureWorkspaceResponse,
219
224
  // Enrollment UX (design 11): the click-Grant approve-page lookup/deny + headless
220
225
  // enroll-token mint.
221
226
  DeviceEnrollmentApproveRequest,
@@ -293,6 +298,7 @@ import type {
293
298
  SwapActiveSandboxRequest,
294
299
  SwapActiveSandboxResponse,
295
300
  ListWorkspaceMembersResponse,
301
+ ListWorkspaceMemberCandidatesResponse,
296
302
  InstallPackRequest,
297
303
  PackInstallationPreview,
298
304
  ListSlackUserLinkAccessRequestsResponse,
@@ -454,6 +460,7 @@ import type {
454
460
  RigChange,
455
461
  ProposeRigChangeRequest,
456
462
  WorkspaceMember,
463
+ WorkspaceMemberCandidate,
457
464
  WorkspaceMemorySearchRequest,
458
465
  WorkspaceMemorySearchResponse,
459
466
  WorkspaceRegisteredPack,
@@ -504,6 +511,7 @@ import type {
504
511
  } from "./workspace-learning";
505
512
  import type {
506
513
  ActivateCompanyProfileRevisionRequest,
514
+ CompanyProfileAgentPolicy,
507
515
  CompanyProfileDiffRequest,
508
516
  CompanyProfileDiffResponse,
509
517
  CompanyProfileListOptions,
@@ -512,6 +520,7 @@ import type {
512
520
  CompanyProfileRevision,
513
521
  RollbackCompanyProfileRequest,
514
522
  UpdateCompanyProfileRequest,
523
+ UpdateCompanyProfileAgentPolicyRequest,
515
524
  } from "./company-profile";
516
525
  import type {
517
526
  WorkspaceStateExportResponse,
@@ -594,6 +603,12 @@ export type SharedSessionReadOptions = {
594
603
  * client has a `beginSharedRead` clock.
595
604
  */
596
605
  onRequestStart?: ((readGeneration?: number) => void) | undefined;
606
+ /**
607
+ * Stop this caller's interest in the shared read. The native request is
608
+ * aborted only after every caller that joined the same generation has
609
+ * stopped waiting for it.
610
+ */
611
+ signal?: AbortSignal | undefined;
597
612
  };
598
613
 
599
614
  export type GetSessionOptions = SharedSessionReadOptions & {
@@ -607,6 +622,8 @@ export type GetSessionOptions = SharedSessionReadOptions & {
607
622
  export type GetSessionLineageOptions = SharedSessionReadOptions;
608
623
 
609
624
  type SingleFlightReadEntry = {
625
+ controller: AbortController;
626
+ consumers: number;
610
627
  generation: number;
611
628
  promise: Promise<unknown>;
612
629
  resolve: (value: unknown) => void;
@@ -627,6 +644,8 @@ function createSingleFlightReadEntry(
627
644
  reject = entryReject;
628
645
  });
629
646
  return {
647
+ controller: new AbortController(),
648
+ consumers: 0,
630
649
  generation,
631
650
  promise,
632
651
  resolve,
@@ -693,6 +712,29 @@ export type FinalizeTranscriptionRecordingInput = {
693
712
  signal?: AbortSignal | undefined;
694
713
  };
695
714
 
715
+ function normalizeScheduledTaskMachineTarget<
716
+ T extends CreateScheduledTaskRequest | UpdateScheduledTaskRequest,
717
+ >(request: T): T {
718
+ if (!("agentConfig" in request) || !request.agentConfig?.machineTarget) {
719
+ return request;
720
+ }
721
+ const { workingDir, ...machineTarget } = request.agentConfig.machineTarget;
722
+ if (workingDir === undefined) {
723
+ return request;
724
+ }
725
+ const normalizedWorkingDir = workingDir.trim();
726
+ return {
727
+ ...request,
728
+ agentConfig: {
729
+ ...request.agentConfig,
730
+ machineTarget: {
731
+ ...machineTarget,
732
+ ...(normalizedWorkingDir ? { workingDir: normalizedWorkingDir } : {}),
733
+ },
734
+ },
735
+ };
736
+ }
737
+
696
738
  /**
697
739
  * Typed client for the OpenGeni public API. Framework-agnostic: only needs
698
740
  * WHATWG `fetch` + streams, so it runs in Node 18+, Bun, Deno, browsers, and
@@ -981,7 +1023,11 @@ export class OpenGeniClient {
981
1023
  options: GetSessionOptions = {},
982
1024
  ): Promise<Session> {
983
1025
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}`;
984
- return await this.sharedRead(path, () => this.requestJson<Session>("GET", path), options);
1026
+ return await this.sharedRead(
1027
+ path,
1028
+ (signal) => this.requestJson<Session>("GET", path, undefined, {}, { signal }),
1029
+ options,
1030
+ );
985
1031
  }
986
1032
 
987
1033
  async updateSession(
@@ -1135,6 +1181,8 @@ export class OpenGeniClient {
1135
1181
  pinsOnly?: boolean;
1136
1182
  /** Return archived root chats instead of the active session list. */
1137
1183
  archivedOnly?: boolean;
1184
+ /** Stop this caller's finite page read when its owning route is abandoned. */
1185
+ signal?: AbortSignal | undefined;
1138
1186
  } = {},
1139
1187
  ): Promise<SessionListResponse> {
1140
1188
  const search = options.search?.trim();
@@ -1152,6 +1200,7 @@ export class OpenGeniClient {
1152
1200
  ...(options.pinsOnly ? { pinsOnly: "true" } : {}),
1153
1201
  ...(options.archivedOnly ? { archivedOnly: "true" } : {}),
1154
1202
  },
1203
+ { signal: options.signal },
1155
1204
  );
1156
1205
  } catch (error) {
1157
1206
  if (error instanceof OpenGeniApiError && error.status === 410) {
@@ -1303,30 +1352,35 @@ export class OpenGeniClient {
1303
1352
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
1304
1353
  return await this.sharedRead(
1305
1354
  path,
1306
- () => this.requestJson<SessionLineageResponse>("GET", path),
1355
+ (signal) => this.requestJson<SessionLineageResponse>("GET", path, undefined, {}, { signal }),
1307
1356
  options,
1308
1357
  );
1309
1358
  }
1310
1359
 
1311
1360
  private sharedRead<T>(
1312
1361
  key: string,
1313
- read: () => Promise<T>,
1362
+ read: (signal: AbortSignal) => Promise<T>,
1314
1363
  options: GetSessionOptions = {},
1315
1364
  ): Promise<T> {
1365
+ if (options.signal?.aborted) {
1366
+ return Promise.reject(
1367
+ options.signal.reason ?? new DOMException("Request aborted", "AbortError"),
1368
+ );
1369
+ }
1316
1370
  const existing = this.active.get(key);
1317
1371
  if (existing) {
1318
1372
  if (!options.fresh) {
1319
1373
  this.observeRead(existing, options.onRequestStart);
1320
- return existing.promise as Promise<T>;
1374
+ return this.consumeSharedRead(key, existing, options.signal);
1321
1375
  }
1322
1376
  const requiredGeneration = existing.generation + 1;
1323
1377
  const queued = this.queued.get(key);
1324
1378
  if (queued && queued.generation >= requiredGeneration) {
1325
1379
  this.observeRead(queued, options.onRequestStart);
1326
- return queued.promise as Promise<T>;
1380
+ return this.consumeSharedRead(key, queued, options.signal);
1327
1381
  }
1328
1382
  const predecessor = queued?.promise ?? existing.promise;
1329
- return this.queueRead(key, predecessor, requiredGeneration, read, options.onRequestStart);
1383
+ return this.queueRead(key, predecessor, requiredGeneration, read, options);
1330
1384
  }
1331
1385
  // The active entry clears in its finally before reactions on its public
1332
1386
  // promise run. During that settlement gap a successor may already be
@@ -1335,31 +1389,27 @@ export class OpenGeniClient {
1335
1389
  const queued = this.queued.get(key);
1336
1390
  if (queued && (!options.fresh || !queued.started)) {
1337
1391
  this.observeRead(queued, options.onRequestStart);
1338
- return queued.promise as Promise<T>;
1392
+ return this.consumeSharedRead(key, queued, options.signal);
1339
1393
  }
1340
1394
  if (queued) {
1341
- return this.queueRead(
1342
- key,
1343
- queued.promise,
1344
- queued.generation + 1,
1345
- read,
1346
- options.onRequestStart,
1347
- );
1395
+ return this.queueRead(key, queued.promise, queued.generation + 1, read, options);
1348
1396
  }
1349
1397
  const generation = (this.generations.get(key) ?? 0) + 1;
1350
1398
  const entry = createSingleFlightReadEntry(generation, options.onRequestStart);
1399
+ const consumed = this.consumeSharedRead<T>(key, entry, options.signal);
1351
1400
  this.launchRead(key, entry, read);
1352
- return entry.promise as Promise<T>;
1401
+ return consumed;
1353
1402
  }
1354
1403
 
1355
1404
  private queueRead<T>(
1356
1405
  key: string,
1357
1406
  predecessor: Promise<unknown>,
1358
1407
  generation: number,
1359
- read: () => Promise<T>,
1360
- onRequestStart?: (readGeneration?: number) => void,
1408
+ read: (signal: AbortSignal) => Promise<T>,
1409
+ options: GetSessionOptions,
1361
1410
  ): Promise<T> {
1362
- const entry = createSingleFlightReadEntry(generation, onRequestStart);
1411
+ const entry = createSingleFlightReadEntry(generation, options.onRequestStart);
1412
+ const consumed = this.consumeSharedRead<T>(key, entry, options.signal);
1363
1413
  this.queued.set(key, entry);
1364
1414
  const launch = () => this.launchRead(key, entry, read);
1365
1415
  void predecessor.then(launch, launch);
@@ -1369,10 +1419,15 @@ export class OpenGeniClient {
1369
1419
  if (!this.active.has(key)) this.generations.delete(key);
1370
1420
  };
1371
1421
  void entry.promise.then(clear, clear);
1372
- return entry.promise as Promise<T>;
1422
+ return consumed;
1373
1423
  }
1374
1424
 
1375
- private launchRead<T>(key: string, entry: SingleFlightReadEntry, read: () => Promise<T>): void {
1425
+ private launchRead<T>(
1426
+ key: string,
1427
+ entry: SingleFlightReadEntry,
1428
+ read: (signal: AbortSignal) => Promise<T>,
1429
+ ): void {
1430
+ if (entry.controller.signal.aborted) return;
1376
1431
  entry.started = true;
1377
1432
  this.generations.set(key, entry.generation);
1378
1433
  this.active.set(key, entry);
@@ -1396,7 +1451,7 @@ export class OpenGeniClient {
1396
1451
  };
1397
1452
  let request: Promise<T>;
1398
1453
  try {
1399
- request = read();
1454
+ request = read(entry.controller.signal);
1400
1455
  } catch (error) {
1401
1456
  settle();
1402
1457
  entry.reject(error);
@@ -1414,6 +1469,33 @@ export class OpenGeniClient {
1414
1469
  );
1415
1470
  }
1416
1471
 
1472
+ private consumeSharedRead<T>(
1473
+ key: string,
1474
+ entry: SingleFlightReadEntry,
1475
+ signal: AbortSignal | undefined,
1476
+ ): Promise<T> {
1477
+ entry.consumers += 1;
1478
+ let released = false;
1479
+ const release = () => {
1480
+ if (released) return;
1481
+ released = true;
1482
+ entry.consumers -= 1;
1483
+ if (!signal?.aborted || entry.consumers > 0) return;
1484
+ const reason = signal.reason;
1485
+ entry.controller.abort(reason);
1486
+ if (this.active.get(key) === entry) this.active.delete(key);
1487
+ if (this.queued.get(key) === entry) this.queued.delete(key);
1488
+ if (!this.active.has(key) && !this.queued.has(key)) this.generations.delete(key);
1489
+ entry.reject(reason);
1490
+ };
1491
+ signal?.addEventListener("abort", release, { once: true });
1492
+ const consumed = awaitWithAbort(entry.promise as Promise<T>, signal);
1493
+ return consumed.finally(() => {
1494
+ signal?.removeEventListener("abort", release);
1495
+ release();
1496
+ });
1497
+ }
1498
+
1417
1499
  private observeRead(
1418
1500
  entry: SingleFlightReadEntry,
1419
1501
  listener: ((readGeneration?: number) => void) | undefined,
@@ -1557,7 +1639,7 @@ export class OpenGeniClient {
1557
1639
  async listTurns(
1558
1640
  workspaceId: string,
1559
1641
  sessionId: string,
1560
- options: { limit?: number; latestStarted?: boolean } = {},
1642
+ options: { limit?: number; latestStarted?: boolean; signal?: AbortSignal | undefined } = {},
1561
1643
  ): Promise<SessionTurn[]> {
1562
1644
  return await this.requestJson<SessionTurn[]>(
1563
1645
  "GET",
@@ -1567,13 +1649,19 @@ export class OpenGeniClient {
1567
1649
  ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
1568
1650
  ...(options.latestStarted ? { latestStarted: "1" } : {}),
1569
1651
  },
1652
+ { signal: options.signal },
1570
1653
  );
1571
1654
  }
1572
1655
 
1573
1656
  /** Newest turn that durably emitted `turn.started`, or null before any admission. */
1574
- async getLatestStartedTurn(workspaceId: string, sessionId: string): Promise<SessionTurn | null> {
1657
+ async getLatestStartedTurn(
1658
+ workspaceId: string,
1659
+ sessionId: string,
1660
+ options: OpenGeniRequestOptions = {},
1661
+ ): Promise<SessionTurn | null> {
1575
1662
  const turns = await this.listTurns(workspaceId, sessionId, {
1576
1663
  latestStarted: true,
1664
+ signal: options.signal,
1577
1665
  });
1578
1666
  return turns[0] ?? null;
1579
1667
  }
@@ -2147,10 +2235,16 @@ export class OpenGeniClient {
2147
2235
  );
2148
2236
  }
2149
2237
 
2150
- async getComposerDraft(workspaceId: string, sessionId: string): Promise<ComposerDraft> {
2238
+ async getComposerDraft(
2239
+ workspaceId: string,
2240
+ sessionId: string,
2241
+ options: OpenGeniRequestOptions = {},
2242
+ ): Promise<ComposerDraft> {
2151
2243
  return await this.requestSessionCommand<ComposerDraft>(
2152
2244
  "GET",
2153
2245
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
2246
+ undefined,
2247
+ options,
2154
2248
  );
2155
2249
  }
2156
2250
 
@@ -2424,9 +2518,17 @@ export class OpenGeniClient {
2424
2518
  // --- Goals -------------------------------------------------------------------
2425
2519
 
2426
2520
  /** The session's goal. 404s when the session never had one. */
2427
- async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
2521
+ async getGoal(
2522
+ workspaceId: string,
2523
+ sessionId: string,
2524
+ options: SharedSessionReadOptions = {},
2525
+ ): Promise<SessionGoal> {
2428
2526
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
2429
- return await this.sharedRead(path, () => this.requestJson<SessionGoal>("GET", path));
2527
+ return await this.sharedRead(
2528
+ path,
2529
+ (signal) => this.requestJson<SessionGoal>("GET", path, undefined, {}, { signal }),
2530
+ options,
2531
+ );
2430
2532
  }
2431
2533
 
2432
2534
  async updateGoal(
@@ -3787,6 +3889,48 @@ export class OpenGeniClient {
3787
3889
  );
3788
3890
  }
3789
3891
 
3892
+ async captureComputerTarget(
3893
+ workspaceId: string,
3894
+ computerSessionId: string,
3895
+ targetId: string,
3896
+ options: OpenGeniRequestOptions = {},
3897
+ ): Promise<ComputerFrame> {
3898
+ const response = await this.requestResponse(
3899
+ "GET",
3900
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/targets/${encodeURIComponent(targetId)}/screenshot`,
3901
+ {},
3902
+ options,
3903
+ );
3904
+ const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3905
+ if (mediaType !== "image/jpeg" && mediaType !== "image/png") {
3906
+ await cancelResponseBody(response, "computer frame media type is invalid");
3907
+ throw new OpenGeniApiError(502, "computer frame media type is invalid");
3908
+ }
3909
+ const metadataHeader = response.headers.get("x-opengeni-computer-frame");
3910
+ if (!metadataHeader || metadataHeader.length > 32 * 1024) {
3911
+ await cancelResponseBody(response, "computer frame metadata is invalid");
3912
+ throw new OpenGeniApiError(502, "computer frame metadata is invalid");
3913
+ }
3914
+ let metadata: ReturnType<typeof decodeComputerFrameMetadataHeader>;
3915
+ try {
3916
+ metadata = decodeComputerFrameMetadataHeader(metadataHeader);
3917
+ } catch {
3918
+ await cancelResponseBody(response, "computer frame metadata is invalid");
3919
+ throw new OpenGeniApiError(502, "computer frame metadata is invalid");
3920
+ }
3921
+ const bytes = await readBoundedResponseBytes(response, 256 * 1024, null);
3922
+ const mismatchReason = computerFrameEvidenceMismatchReason(metadata, {
3923
+ computerSessionId,
3924
+ targetId,
3925
+ mediaType,
3926
+ sha256: await sha256Hex(bytes),
3927
+ });
3928
+ if (mismatchReason) {
3929
+ throw new OpenGeniApiError(502, "computer frame evidence does not match its request");
3930
+ }
3931
+ return { ...metadata, data: bytes };
3932
+ }
3933
+
3790
3934
  async actInComputer(
3791
3935
  workspaceId: string,
3792
3936
  computerSessionId: string,
@@ -3870,8 +4014,14 @@ export class OpenGeniClient {
3870
4014
  * expected to authenticate. Drives a composer's model picker without prior
3871
4015
  * knowledge of the host setup; safe to call before any auth is established.
3872
4016
  */
3873
- async getClientConfig(): Promise<ClientConfig> {
3874
- const config = await this.requestJson<ClientConfig>("GET", "/v1/config/client");
4017
+ async getClientConfig(options: OpenGeniRequestOptions = {}): Promise<ClientConfig> {
4018
+ const config = await this.requestJson<ClientConfig>(
4019
+ "GET",
4020
+ "/v1/config/client",
4021
+ undefined,
4022
+ {},
4023
+ options,
4024
+ );
3875
4025
  if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
3876
4026
  throw new OpenGeniApiContractMismatchError(
3877
4027
  OPENGENI_API_CONTRACT_REVISION,
@@ -3882,10 +4032,16 @@ export class OpenGeniClient {
3882
4032
  }
3883
4033
 
3884
4034
  /** Authenticated model definitions plus workspace-specific selectability. */
3885
- async getWorkspaceModelCatalog(workspaceId: string): Promise<WorkspaceModelCatalogResponse> {
4035
+ async getWorkspaceModelCatalog(
4036
+ workspaceId: string,
4037
+ options: OpenGeniRequestOptions = {},
4038
+ ): Promise<WorkspaceModelCatalogResponse> {
3886
4039
  return await this.requestJson<WorkspaceModelCatalogResponse>(
3887
4040
  "GET",
3888
4041
  `/v1/workspaces/${workspaceId}/model-catalog`,
4042
+ undefined,
4043
+ {},
4044
+ options,
3889
4045
  );
3890
4046
  }
3891
4047
 
@@ -3912,10 +4068,14 @@ export class OpenGeniClient {
3912
4068
  /** Authenticated realtime voice models and credential readiness. */
3913
4069
  async getWorkspaceRealtimeModelCatalog(
3914
4070
  workspaceId: string,
4071
+ options: OpenGeniRequestOptions = {},
3915
4072
  ): Promise<WorkspaceRealtimeModelCatalogResponse> {
3916
4073
  return await this.requestJson<WorkspaceRealtimeModelCatalogResponse>(
3917
4074
  "GET",
3918
4075
  `/v1/workspaces/${workspaceId}/realtime-model-catalog`,
4076
+ undefined,
4077
+ {},
4078
+ options,
3919
4079
  );
3920
4080
  }
3921
4081
 
@@ -4271,6 +4431,18 @@ export class OpenGeniClient {
4271
4431
  return await this.requestJson<Workspace>("POST", "/v1/workspaces", request);
4272
4432
  }
4273
4433
 
4434
+ /**
4435
+ * Create an organization workspace once for a stable external tenant
4436
+ * identity, or return the existing workspace without overwriting it.
4437
+ */
4438
+ async ensureWorkspace(request: EnsureWorkspaceRequest): Promise<EnsureWorkspaceResponse> {
4439
+ return await this.requestJson<EnsureWorkspaceResponse>(
4440
+ "PUT",
4441
+ "/v1/workspaces/external",
4442
+ request,
4443
+ );
4444
+ }
4445
+
4274
4446
  async getWorkspace(workspaceId: string): Promise<Workspace> {
4275
4447
  return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
4276
4448
  }
@@ -4531,6 +4703,24 @@ export class OpenGeniClient {
4531
4703
  );
4532
4704
  }
4533
4705
 
4706
+ async getCompanyProfileAgentPolicy(workspaceId: string): Promise<CompanyProfileAgentPolicy> {
4707
+ return await this.requestJson<CompanyProfileAgentPolicy>(
4708
+ "GET",
4709
+ `/v1/workspaces/${workspaceId}/company-profile/agent-policy`,
4710
+ );
4711
+ }
4712
+
4713
+ async updateCompanyProfileAgentPolicy(
4714
+ workspaceId: string,
4715
+ request: UpdateCompanyProfileAgentPolicyRequest,
4716
+ ): Promise<CompanyProfileAgentPolicy> {
4717
+ return await this.requestJson<CompanyProfileAgentPolicy>(
4718
+ "PATCH",
4719
+ `/v1/workspaces/${workspaceId}/company-profile/agent-policy`,
4720
+ request,
4721
+ );
4722
+ }
4723
+
4534
4724
  async updateCompanyProfile(
4535
4725
  workspaceId: string,
4536
4726
  request: UpdateCompanyProfileRequest,
@@ -4721,9 +4911,17 @@ export class OpenGeniClient {
4721
4911
  return response.members;
4722
4912
  }
4723
4913
 
4914
+ /** Active organization members who can be added to this workspace. */
4915
+ async listWorkspaceMemberCandidates(workspaceId: string): Promise<WorkspaceMemberCandidate[]> {
4916
+ const response = await this.requestJson<ListWorkspaceMemberCandidatesResponse>(
4917
+ "GET",
4918
+ `/v1/workspaces/${workspaceId}/member-candidates`,
4919
+ );
4920
+ return response.members;
4921
+ }
4922
+
4724
4923
  /**
4725
- * Add an already-registered user by email. 404s when no user with that email
4726
- * exists (email invites for unknown users are deferred).
4924
+ * Add one active member selected from the workspace candidate inventory.
4727
4925
  */
4728
4926
  async addWorkspaceMember(
4729
4927
  workspaceId: string,
@@ -4848,7 +5046,7 @@ export class OpenGeniClient {
4848
5046
  return await this.requestJson<ScheduledTask>(
4849
5047
  "POST",
4850
5048
  `/v1/workspaces/${workspaceId}/scheduled-tasks`,
4851
- request,
5049
+ normalizeScheduledTaskMachineTarget(request),
4852
5050
  );
4853
5051
  }
4854
5052
 
@@ -4860,7 +5058,7 @@ export class OpenGeniClient {
4860
5058
  return await this.requestJson<ScheduledTask>(
4861
5059
  "PATCH",
4862
5060
  `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
4863
- request,
5061
+ normalizeScheduledTaskMachineTarget(request),
4864
5062
  );
4865
5063
  }
4866
5064
 
@@ -6755,6 +6953,34 @@ export class OpenGeniClient {
6755
6953
  );
6756
6954
  }
6757
6955
 
6956
+ async listOrganizationApiKeys(organizationId: string): Promise<ApiKey[]> {
6957
+ const response = await this.requestJson<ListApiKeysResponse>(
6958
+ "GET",
6959
+ `/v1/organizations/${organizationId}/api-keys`,
6960
+ );
6961
+ return response.apiKeys;
6962
+ }
6963
+
6964
+ /** The returned `token` is shown once; only its prefix is stored. */
6965
+ async createOrganizationApiKey(
6966
+ organizationId: string,
6967
+ request: CreateOrganizationApiKeyRequest,
6968
+ ): Promise<CreateApiKeyResponse> {
6969
+ return await this.requestJson<CreateApiKeyResponse>(
6970
+ "POST",
6971
+ `/v1/organizations/${organizationId}/api-keys`,
6972
+ request,
6973
+ );
6974
+ }
6975
+
6976
+ /** Revoke an organization-scoped API key. Returns the revoked key. */
6977
+ async deleteOrganizationApiKey(organizationId: string, apiKeyId: string): Promise<ApiKey> {
6978
+ return await this.requestJson<ApiKey>(
6979
+ "DELETE",
6980
+ `/v1/organizations/${organizationId}/api-keys/${apiKeyId}`,
6981
+ );
6982
+ }
6983
+
6758
6984
  // --- Billing (account-scoped) --------------------------------------------------------------------
6759
6985
 
6760
6986
  async getBilling(options: { accountId?: string } = {}): Promise<BillingSummary> {
@@ -7105,14 +7331,20 @@ export class OpenGeniClient {
7105
7331
  }
7106
7332
 
7107
7333
  /** Contract-checked JSON transport shared by opt-in typed SDK clients. */
7108
- private async requestSessionCommand<T>(method: string, path: string, body?: unknown): Promise<T> {
7334
+ private async requestSessionCommand<T>(
7335
+ method: string,
7336
+ path: string,
7337
+ body?: unknown,
7338
+ options: OpenGeniRequestOptions = {},
7339
+ ): Promise<T> {
7109
7340
  return await this.requestJson<T>(
7110
7341
  method,
7111
7342
  path,
7112
7343
  body,
7113
7344
  {},
7114
7345
  {
7115
- timeoutMs: this.sessionCommandTimeoutMs,
7346
+ signal: options.signal,
7347
+ timeoutMs: options.timeoutMs ?? this.sessionCommandTimeoutMs,
7116
7348
  },
7117
7349
  );
7118
7350
  }
@@ -7486,6 +7718,30 @@ async function sha256Hex(bytes: Uint8Array): Promise<string> {
7486
7718
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
7487
7719
  }
7488
7720
 
7721
+ type ComputerFrameEvidenceMismatchReason =
7722
+ | "frame_session_mismatch"
7723
+ | "frame_target_mismatch"
7724
+ | "frame_media_mismatch"
7725
+ | "frame_digest_mismatch";
7726
+
7727
+ function computerFrameEvidenceMismatchReason(
7728
+ metadata: ReturnType<typeof decodeComputerFrameMetadataHeader>,
7729
+ expected: {
7730
+ computerSessionId: string;
7731
+ targetId: string;
7732
+ mediaType: "image/jpeg" | "image/png";
7733
+ sha256: string;
7734
+ },
7735
+ ): ComputerFrameEvidenceMismatchReason | null {
7736
+ if (metadata.computerSessionId !== expected.computerSessionId) {
7737
+ return "frame_session_mismatch";
7738
+ }
7739
+ if (metadata.targetId !== expected.targetId) return "frame_target_mismatch";
7740
+ if (metadata.mediaType !== expected.mediaType) return "frame_media_mismatch";
7741
+ if (metadata.sha256 !== expected.sha256) return "frame_digest_mismatch";
7742
+ return null;
7743
+ }
7744
+
7489
7745
  async function cancelResponseBody(response: Response, reason: string): Promise<void> {
7490
7746
  await response.body?.cancel(reason).catch(() => undefined);
7491
7747
  }
@@ -82,6 +82,19 @@ export type CompanyProfileMutationResponse = {
82
82
  head: CompanyProfileHead | null;
83
83
  event: CompanyProfileActivationEvent | null;
84
84
  };
85
+ export type CompanyProfileAgentPolicyMode = "off" | "suggest" | "automatic";
86
+ export type CompanyProfileAgentPolicy = {
87
+ organizationId: string;
88
+ mode: CompanyProfileAgentPolicyMode;
89
+ version: number;
90
+ updatedAt: string;
91
+ changed?: boolean;
92
+ };
93
+ export type UpdateCompanyProfileAgentPolicyRequest = {
94
+ mode: CompanyProfileAgentPolicyMode;
95
+ expectedVersion: number;
96
+ operationId: string;
97
+ };
85
98
  export type CompanyProfileDiffRequest = { fromRevisionId: string; toRevisionId: string };
86
99
  export type CompanyProfileDiffResponse = {
87
100
  from: CompanyProfileRevision;