@opengeni/sdk 3.1.0 → 3.3.2-canary.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 (58) 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 +6 -6
  6. package/dist/browser.d.ts +2 -1
  7. package/dist/browser.js +6 -4
  8. package/dist/{chunk-C2H7HBNP.js → chunk-6NEPJ7NC.js} +2 -2
  9. package/dist/{chunk-TX3EIENU.js → chunk-BESZFWMU.js} +2 -2
  10. package/dist/{chunk-YTAWBDO2.js → chunk-E5BNEG2D.js} +2 -1
  11. package/dist/chunk-E5BNEG2D.js.map +1 -0
  12. package/dist/{chunk-7ZXBPJZP.js → chunk-GY6RQPCG.js} +282 -43
  13. package/dist/chunk-GY6RQPCG.js.map +1 -0
  14. package/dist/{chunk-C4DJPZRU.js → chunk-OO5LPTO7.js} +14 -1
  15. package/dist/chunk-OO5LPTO7.js.map +1 -0
  16. package/dist/{chunk-GW3EJ2GP.js → chunk-QTBAMHEF.js} +28 -3
  17. package/dist/{chunk-GW3EJ2GP.js.map → chunk-QTBAMHEF.js.map} +1 -1
  18. package/dist/{chunk-VUQZAB3O.js → chunk-TFCLHRZK.js} +2 -2
  19. package/dist/client.d.ts +47 -11
  20. package/dist/codex-realtime-controller.js +2 -2
  21. package/dist/company-profile.d.ts +13 -0
  22. package/dist/core.d.ts +2 -1
  23. package/dist/core.js +7 -5
  24. package/dist/document-authority.js +5 -5
  25. package/dist/editable-artifacts.js +1 -1
  26. package/dist/errors.d.ts +12 -0
  27. package/dist/index.d.ts +6 -3
  28. package/dist/index.js +219 -7
  29. package/dist/index.js.map +1 -1
  30. package/dist/interaction.d.ts +3 -0
  31. package/dist/interaction.js +4 -2
  32. package/dist/model-picker-order.d.ts +1 -0
  33. package/dist/model-picker-order.js +10 -0
  34. package/dist/model-picker-order.js.map +1 -0
  35. package/dist/realtime.d.ts +3 -1
  36. package/dist/realtime.js +2 -2
  37. package/dist/realtime.js.map +1 -1
  38. package/dist/session-titles.d.ts +28 -0
  39. package/dist/types.d.ts +90 -3
  40. package/package.json +6 -2
  41. package/src/accounts.ts +18 -0
  42. package/src/browser.ts +2 -0
  43. package/src/client.ts +407 -35
  44. package/src/company-profile.ts +13 -0
  45. package/src/core.ts +2 -0
  46. package/src/errors.ts +23 -0
  47. package/src/index.ts +29 -0
  48. package/src/interaction.ts +32 -0
  49. package/src/model-picker-order.ts +6 -0
  50. package/src/realtime.ts +1 -0
  51. package/src/session-titles.ts +84 -0
  52. package/src/types.ts +105 -2
  53. package/dist/chunk-7ZXBPJZP.js.map +0 -1
  54. package/dist/chunk-C4DJPZRU.js.map +0 -1
  55. package/dist/chunk-YTAWBDO2.js.map +0 -1
  56. /package/dist/{chunk-C2H7HBNP.js.map → chunk-6NEPJ7NC.js.map} +0 -0
  57. /package/dist/{chunk-TX3EIENU.js.map → chunk-BESZFWMU.js.map} +0 -0
  58. /package/dist/{chunk-VUQZAB3O.js.map → chunk-TFCLHRZK.js.map} +0 -0
package/src/client.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  OpenGeniApiContractMismatchError,
3
3
  OpenGeniApiError,
4
+ OpenGeniSecureContextRequiredError,
4
5
  OpenGeniSessionListCursorError,
5
6
  } from "./errors";
6
7
  import {
@@ -23,6 +24,7 @@ import {
23
24
  } from "./workspace-live-stream";
24
25
  import {
25
26
  OpenGeniInteractionClient,
27
+ decodeComputerFrameMetadataHeader,
26
28
  type AuthRun,
27
29
  type AuthRunListOptions,
28
30
  type AuthRunListResponse,
@@ -57,6 +59,7 @@ import {
57
59
  type ComputerActionReceipt,
58
60
  type ComputerActionRequest,
59
61
  type ComputerClipboard,
62
+ type ComputerFrame,
60
63
  type ComputerObservation,
61
64
  type ComputerSession,
62
65
  type ComputerSessionAttachment,
@@ -167,6 +170,14 @@ import type {
167
170
  ClientConfig,
168
171
  WorkspaceModelAccessPolicy,
169
172
  WorkspaceModelCatalogResponse,
173
+ WorkspaceGatewayCustomModel,
174
+ WorkspaceGatewayCustomModelsResponse,
175
+ CreateWorkspaceGatewayCustomModelRequest,
176
+ DeleteWorkspaceGatewayCustomModelRequest,
177
+ WorkspaceOpenRouterCustomModel,
178
+ WorkspaceOpenRouterCustomModelsResponse,
179
+ CreateWorkspaceOpenRouterCustomModelRequest,
180
+ DeleteWorkspaceOpenRouterCustomModelRequest,
170
181
  WorkspaceRealtimeModelCatalogResponse,
171
182
  ClientSessionEventInput,
172
183
  UserMessageEventInput,
@@ -184,6 +195,7 @@ import type {
184
195
  VerifyPersonalGitHubRepositorySelectionsRequest,
185
196
  CreateApiKeyRequest,
186
197
  CreateApiKeyResponse,
198
+ CreateOrganizationApiKeyRequest,
187
199
  CreateCapabilityCatalogItemRequest,
188
200
  InstallSkillRequest,
189
201
  InstallLibrarySkillRequest,
@@ -216,6 +228,8 @@ import type {
216
228
  ResolveVariableSetAttachmentsResponse,
217
229
  CreateRigRequest,
218
230
  CreateWorkspaceRequest,
231
+ EnsureWorkspaceRequest,
232
+ EnsureWorkspaceResponse,
219
233
  // Enrollment UX (design 11): the click-Grant approve-page lookup/deny + headless
220
234
  // enroll-token mint.
221
235
  DeviceEnrollmentApproveRequest,
@@ -293,6 +307,7 @@ import type {
293
307
  SwapActiveSandboxRequest,
294
308
  SwapActiveSandboxResponse,
295
309
  ListWorkspaceMembersResponse,
310
+ ListWorkspaceMemberCandidatesResponse,
296
311
  InstallPackRequest,
297
312
  PackInstallationPreview,
298
313
  ListSlackUserLinkAccessRequestsResponse,
@@ -454,6 +469,7 @@ import type {
454
469
  RigChange,
455
470
  ProposeRigChangeRequest,
456
471
  WorkspaceMember,
472
+ WorkspaceMemberCandidate,
457
473
  WorkspaceMemorySearchRequest,
458
474
  WorkspaceMemorySearchResponse,
459
475
  WorkspaceRegisteredPack,
@@ -504,6 +520,7 @@ import type {
504
520
  } from "./workspace-learning";
505
521
  import type {
506
522
  ActivateCompanyProfileRevisionRequest,
523
+ CompanyProfileAgentPolicy,
507
524
  CompanyProfileDiffRequest,
508
525
  CompanyProfileDiffResponse,
509
526
  CompanyProfileListOptions,
@@ -512,6 +529,7 @@ import type {
512
529
  CompanyProfileRevision,
513
530
  RollbackCompanyProfileRequest,
514
531
  UpdateCompanyProfileRequest,
532
+ UpdateCompanyProfileAgentPolicyRequest,
515
533
  } from "./company-profile";
516
534
  import type {
517
535
  WorkspaceStateExportResponse,
@@ -594,6 +612,12 @@ export type SharedSessionReadOptions = {
594
612
  * client has a `beginSharedRead` clock.
595
613
  */
596
614
  onRequestStart?: ((readGeneration?: number) => void) | undefined;
615
+ /**
616
+ * Stop this caller's interest in the shared read. The native request is
617
+ * aborted only after every caller that joined the same generation has
618
+ * stopped waiting for it.
619
+ */
620
+ signal?: AbortSignal | undefined;
597
621
  };
598
622
 
599
623
  export type GetSessionOptions = SharedSessionReadOptions & {
@@ -607,6 +631,8 @@ export type GetSessionOptions = SharedSessionReadOptions & {
607
631
  export type GetSessionLineageOptions = SharedSessionReadOptions;
608
632
 
609
633
  type SingleFlightReadEntry = {
634
+ controller: AbortController;
635
+ consumers: number;
610
636
  generation: number;
611
637
  promise: Promise<unknown>;
612
638
  resolve: (value: unknown) => void;
@@ -627,6 +653,8 @@ function createSingleFlightReadEntry(
627
653
  reject = entryReject;
628
654
  });
629
655
  return {
656
+ controller: new AbortController(),
657
+ consumers: 0,
630
658
  generation,
631
659
  promise,
632
660
  resolve,
@@ -693,6 +721,29 @@ export type FinalizeTranscriptionRecordingInput = {
693
721
  signal?: AbortSignal | undefined;
694
722
  };
695
723
 
724
+ function normalizeScheduledTaskMachineTarget<
725
+ T extends CreateScheduledTaskRequest | UpdateScheduledTaskRequest,
726
+ >(request: T): T {
727
+ if (!("agentConfig" in request) || !request.agentConfig?.machineTarget) {
728
+ return request;
729
+ }
730
+ const { workingDir, ...machineTarget } = request.agentConfig.machineTarget;
731
+ if (workingDir === undefined) {
732
+ return request;
733
+ }
734
+ const normalizedWorkingDir = workingDir.trim();
735
+ return {
736
+ ...request,
737
+ agentConfig: {
738
+ ...request.agentConfig,
739
+ machineTarget: {
740
+ ...machineTarget,
741
+ ...(normalizedWorkingDir ? { workingDir: normalizedWorkingDir } : {}),
742
+ },
743
+ },
744
+ };
745
+ }
746
+
696
747
  /**
697
748
  * Typed client for the OpenGeni public API. Framework-agnostic: only needs
698
749
  * WHATWG `fetch` + streams, so it runs in Node 18+, Bun, Deno, browsers, and
@@ -981,7 +1032,11 @@ export class OpenGeniClient {
981
1032
  options: GetSessionOptions = {},
982
1033
  ): Promise<Session> {
983
1034
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}`;
984
- return await this.sharedRead(path, () => this.requestJson<Session>("GET", path), options);
1035
+ return await this.sharedRead(
1036
+ path,
1037
+ (signal) => this.requestJson<Session>("GET", path, undefined, {}, { signal }),
1038
+ options,
1039
+ );
985
1040
  }
986
1041
 
987
1042
  async updateSession(
@@ -1135,6 +1190,8 @@ export class OpenGeniClient {
1135
1190
  pinsOnly?: boolean;
1136
1191
  /** Return archived root chats instead of the active session list. */
1137
1192
  archivedOnly?: boolean;
1193
+ /** Stop this caller's finite page read when its owning route is abandoned. */
1194
+ signal?: AbortSignal | undefined;
1138
1195
  } = {},
1139
1196
  ): Promise<SessionListResponse> {
1140
1197
  const search = options.search?.trim();
@@ -1152,6 +1209,7 @@ export class OpenGeniClient {
1152
1209
  ...(options.pinsOnly ? { pinsOnly: "true" } : {}),
1153
1210
  ...(options.archivedOnly ? { archivedOnly: "true" } : {}),
1154
1211
  },
1212
+ { signal: options.signal },
1155
1213
  );
1156
1214
  } catch (error) {
1157
1215
  if (error instanceof OpenGeniApiError && error.status === 410) {
@@ -1303,30 +1361,35 @@ export class OpenGeniClient {
1303
1361
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/lineage`;
1304
1362
  return await this.sharedRead(
1305
1363
  path,
1306
- () => this.requestJson<SessionLineageResponse>("GET", path),
1364
+ (signal) => this.requestJson<SessionLineageResponse>("GET", path, undefined, {}, { signal }),
1307
1365
  options,
1308
1366
  );
1309
1367
  }
1310
1368
 
1311
1369
  private sharedRead<T>(
1312
1370
  key: string,
1313
- read: () => Promise<T>,
1371
+ read: (signal: AbortSignal) => Promise<T>,
1314
1372
  options: GetSessionOptions = {},
1315
1373
  ): Promise<T> {
1374
+ if (options.signal?.aborted) {
1375
+ return Promise.reject(
1376
+ options.signal.reason ?? new DOMException("Request aborted", "AbortError"),
1377
+ );
1378
+ }
1316
1379
  const existing = this.active.get(key);
1317
1380
  if (existing) {
1318
1381
  if (!options.fresh) {
1319
1382
  this.observeRead(existing, options.onRequestStart);
1320
- return existing.promise as Promise<T>;
1383
+ return this.consumeSharedRead(key, existing, options.signal);
1321
1384
  }
1322
1385
  const requiredGeneration = existing.generation + 1;
1323
1386
  const queued = this.queued.get(key);
1324
1387
  if (queued && queued.generation >= requiredGeneration) {
1325
1388
  this.observeRead(queued, options.onRequestStart);
1326
- return queued.promise as Promise<T>;
1389
+ return this.consumeSharedRead(key, queued, options.signal);
1327
1390
  }
1328
1391
  const predecessor = queued?.promise ?? existing.promise;
1329
- return this.queueRead(key, predecessor, requiredGeneration, read, options.onRequestStart);
1392
+ return this.queueRead(key, predecessor, requiredGeneration, read, options);
1330
1393
  }
1331
1394
  // The active entry clears in its finally before reactions on its public
1332
1395
  // promise run. During that settlement gap a successor may already be
@@ -1335,31 +1398,27 @@ export class OpenGeniClient {
1335
1398
  const queued = this.queued.get(key);
1336
1399
  if (queued && (!options.fresh || !queued.started)) {
1337
1400
  this.observeRead(queued, options.onRequestStart);
1338
- return queued.promise as Promise<T>;
1401
+ return this.consumeSharedRead(key, queued, options.signal);
1339
1402
  }
1340
1403
  if (queued) {
1341
- return this.queueRead(
1342
- key,
1343
- queued.promise,
1344
- queued.generation + 1,
1345
- read,
1346
- options.onRequestStart,
1347
- );
1404
+ return this.queueRead(key, queued.promise, queued.generation + 1, read, options);
1348
1405
  }
1349
1406
  const generation = (this.generations.get(key) ?? 0) + 1;
1350
1407
  const entry = createSingleFlightReadEntry(generation, options.onRequestStart);
1408
+ const consumed = this.consumeSharedRead<T>(key, entry, options.signal);
1351
1409
  this.launchRead(key, entry, read);
1352
- return entry.promise as Promise<T>;
1410
+ return consumed;
1353
1411
  }
1354
1412
 
1355
1413
  private queueRead<T>(
1356
1414
  key: string,
1357
1415
  predecessor: Promise<unknown>,
1358
1416
  generation: number,
1359
- read: () => Promise<T>,
1360
- onRequestStart?: (readGeneration?: number) => void,
1417
+ read: (signal: AbortSignal) => Promise<T>,
1418
+ options: GetSessionOptions,
1361
1419
  ): Promise<T> {
1362
- const entry = createSingleFlightReadEntry(generation, onRequestStart);
1420
+ const entry = createSingleFlightReadEntry(generation, options.onRequestStart);
1421
+ const consumed = this.consumeSharedRead<T>(key, entry, options.signal);
1363
1422
  this.queued.set(key, entry);
1364
1423
  const launch = () => this.launchRead(key, entry, read);
1365
1424
  void predecessor.then(launch, launch);
@@ -1369,10 +1428,15 @@ export class OpenGeniClient {
1369
1428
  if (!this.active.has(key)) this.generations.delete(key);
1370
1429
  };
1371
1430
  void entry.promise.then(clear, clear);
1372
- return entry.promise as Promise<T>;
1431
+ return consumed;
1373
1432
  }
1374
1433
 
1375
- private launchRead<T>(key: string, entry: SingleFlightReadEntry, read: () => Promise<T>): void {
1434
+ private launchRead<T>(
1435
+ key: string,
1436
+ entry: SingleFlightReadEntry,
1437
+ read: (signal: AbortSignal) => Promise<T>,
1438
+ ): void {
1439
+ if (entry.controller.signal.aborted) return;
1376
1440
  entry.started = true;
1377
1441
  this.generations.set(key, entry.generation);
1378
1442
  this.active.set(key, entry);
@@ -1396,7 +1460,7 @@ export class OpenGeniClient {
1396
1460
  };
1397
1461
  let request: Promise<T>;
1398
1462
  try {
1399
- request = read();
1463
+ request = read(entry.controller.signal);
1400
1464
  } catch (error) {
1401
1465
  settle();
1402
1466
  entry.reject(error);
@@ -1414,6 +1478,33 @@ export class OpenGeniClient {
1414
1478
  );
1415
1479
  }
1416
1480
 
1481
+ private consumeSharedRead<T>(
1482
+ key: string,
1483
+ entry: SingleFlightReadEntry,
1484
+ signal: AbortSignal | undefined,
1485
+ ): Promise<T> {
1486
+ entry.consumers += 1;
1487
+ let released = false;
1488
+ const release = () => {
1489
+ if (released) return;
1490
+ released = true;
1491
+ entry.consumers -= 1;
1492
+ if (!signal?.aborted || entry.consumers > 0) return;
1493
+ const reason = signal.reason;
1494
+ entry.controller.abort(reason);
1495
+ if (this.active.get(key) === entry) this.active.delete(key);
1496
+ if (this.queued.get(key) === entry) this.queued.delete(key);
1497
+ if (!this.active.has(key) && !this.queued.has(key)) this.generations.delete(key);
1498
+ entry.reject(reason);
1499
+ };
1500
+ signal?.addEventListener("abort", release, { once: true });
1501
+ const consumed = awaitWithAbort(entry.promise as Promise<T>, signal);
1502
+ return consumed.finally(() => {
1503
+ signal?.removeEventListener("abort", release);
1504
+ release();
1505
+ });
1506
+ }
1507
+
1417
1508
  private observeRead(
1418
1509
  entry: SingleFlightReadEntry,
1419
1510
  listener: ((readGeneration?: number) => void) | undefined,
@@ -1557,7 +1648,7 @@ export class OpenGeniClient {
1557
1648
  async listTurns(
1558
1649
  workspaceId: string,
1559
1650
  sessionId: string,
1560
- options: { limit?: number; latestStarted?: boolean } = {},
1651
+ options: { limit?: number; latestStarted?: boolean; signal?: AbortSignal | undefined } = {},
1561
1652
  ): Promise<SessionTurn[]> {
1562
1653
  return await this.requestJson<SessionTurn[]>(
1563
1654
  "GET",
@@ -1567,13 +1658,19 @@ export class OpenGeniClient {
1567
1658
  ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
1568
1659
  ...(options.latestStarted ? { latestStarted: "1" } : {}),
1569
1660
  },
1661
+ { signal: options.signal },
1570
1662
  );
1571
1663
  }
1572
1664
 
1573
1665
  /** Newest turn that durably emitted `turn.started`, or null before any admission. */
1574
- async getLatestStartedTurn(workspaceId: string, sessionId: string): Promise<SessionTurn | null> {
1666
+ async getLatestStartedTurn(
1667
+ workspaceId: string,
1668
+ sessionId: string,
1669
+ options: OpenGeniRequestOptions = {},
1670
+ ): Promise<SessionTurn | null> {
1575
1671
  const turns = await this.listTurns(workspaceId, sessionId, {
1576
1672
  latestStarted: true,
1673
+ signal: options.signal,
1577
1674
  });
1578
1675
  return turns[0] ?? null;
1579
1676
  }
@@ -2147,10 +2244,16 @@ export class OpenGeniClient {
2147
2244
  );
2148
2245
  }
2149
2246
 
2150
- async getComposerDraft(workspaceId: string, sessionId: string): Promise<ComposerDraft> {
2247
+ async getComposerDraft(
2248
+ workspaceId: string,
2249
+ sessionId: string,
2250
+ options: OpenGeniRequestOptions = {},
2251
+ ): Promise<ComposerDraft> {
2151
2252
  return await this.requestSessionCommand<ComposerDraft>(
2152
2253
  "GET",
2153
2254
  `/v1/workspaces/${workspaceId}/sessions/${sessionId}/composer-draft`,
2255
+ undefined,
2256
+ options,
2154
2257
  );
2155
2258
  }
2156
2259
 
@@ -2424,9 +2527,17 @@ export class OpenGeniClient {
2424
2527
  // --- Goals -------------------------------------------------------------------
2425
2528
 
2426
2529
  /** The session's goal. 404s when the session never had one. */
2427
- async getGoal(workspaceId: string, sessionId: string): Promise<SessionGoal> {
2530
+ async getGoal(
2531
+ workspaceId: string,
2532
+ sessionId: string,
2533
+ options: SharedSessionReadOptions = {},
2534
+ ): Promise<SessionGoal> {
2428
2535
  const path = `/v1/workspaces/${workspaceId}/sessions/${sessionId}/goal`;
2429
- return await this.sharedRead(path, () => this.requestJson<SessionGoal>("GET", path));
2536
+ return await this.sharedRead(
2537
+ path,
2538
+ (signal) => this.requestJson<SessionGoal>("GET", path, undefined, {}, { signal }),
2539
+ options,
2540
+ );
2430
2541
  }
2431
2542
 
2432
2543
  async updateGoal(
@@ -3787,6 +3898,48 @@ export class OpenGeniClient {
3787
3898
  );
3788
3899
  }
3789
3900
 
3901
+ async captureComputerTarget(
3902
+ workspaceId: string,
3903
+ computerSessionId: string,
3904
+ targetId: string,
3905
+ options: OpenGeniRequestOptions = {},
3906
+ ): Promise<ComputerFrame> {
3907
+ const response = await this.requestResponse(
3908
+ "GET",
3909
+ `/v1/workspaces/${workspaceId}/computer-sessions/${encodeURIComponent(computerSessionId)}/targets/${encodeURIComponent(targetId)}/screenshot`,
3910
+ {},
3911
+ options,
3912
+ );
3913
+ const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3914
+ if (mediaType !== "image/jpeg" && mediaType !== "image/png") {
3915
+ await cancelResponseBody(response, "computer frame media type is invalid");
3916
+ throw new OpenGeniApiError(502, "computer frame media type is invalid");
3917
+ }
3918
+ const metadataHeader = response.headers.get("x-opengeni-computer-frame");
3919
+ if (!metadataHeader || metadataHeader.length > 32 * 1024) {
3920
+ await cancelResponseBody(response, "computer frame metadata is invalid");
3921
+ throw new OpenGeniApiError(502, "computer frame metadata is invalid");
3922
+ }
3923
+ let metadata: ReturnType<typeof decodeComputerFrameMetadataHeader>;
3924
+ try {
3925
+ metadata = decodeComputerFrameMetadataHeader(metadataHeader);
3926
+ } catch {
3927
+ await cancelResponseBody(response, "computer frame metadata is invalid");
3928
+ throw new OpenGeniApiError(502, "computer frame metadata is invalid");
3929
+ }
3930
+ const bytes = await readBoundedResponseBytes(response, 256 * 1024, null);
3931
+ const mismatchReason = computerFrameEvidenceMismatchReason(metadata, {
3932
+ computerSessionId,
3933
+ targetId,
3934
+ mediaType,
3935
+ sha256: await sha256Hex(bytes),
3936
+ });
3937
+ if (mismatchReason) {
3938
+ throw new OpenGeniApiError(502, "computer frame evidence does not match its request");
3939
+ }
3940
+ return { ...metadata, data: bytes };
3941
+ }
3942
+
3790
3943
  async actInComputer(
3791
3944
  workspaceId: string,
3792
3945
  computerSessionId: string,
@@ -3870,8 +4023,14 @@ export class OpenGeniClient {
3870
4023
  * expected to authenticate. Drives a composer's model picker without prior
3871
4024
  * knowledge of the host setup; safe to call before any auth is established.
3872
4025
  */
3873
- async getClientConfig(): Promise<ClientConfig> {
3874
- const config = await this.requestJson<ClientConfig>("GET", "/v1/config/client");
4026
+ async getClientConfig(options: OpenGeniRequestOptions = {}): Promise<ClientConfig> {
4027
+ const config = await this.requestJson<ClientConfig>(
4028
+ "GET",
4029
+ "/v1/config/client",
4030
+ undefined,
4031
+ {},
4032
+ options,
4033
+ );
3875
4034
  if (config.apiContractRevision !== OPENGENI_API_CONTRACT_REVISION) {
3876
4035
  throw new OpenGeniApiContractMismatchError(
3877
4036
  OPENGENI_API_CONTRACT_REVISION,
@@ -3882,10 +4041,100 @@ export class OpenGeniClient {
3882
4041
  }
3883
4042
 
3884
4043
  /** Authenticated model definitions plus workspace-specific selectability. */
3885
- async getWorkspaceModelCatalog(workspaceId: string): Promise<WorkspaceModelCatalogResponse> {
4044
+ async getWorkspaceModelCatalog(
4045
+ workspaceId: string,
4046
+ options: OpenGeniRequestOptions = {},
4047
+ ): Promise<WorkspaceModelCatalogResponse> {
3886
4048
  return await this.requestJson<WorkspaceModelCatalogResponse>(
3887
4049
  "GET",
3888
4050
  `/v1/workspaces/${workspaceId}/model-catalog`,
4051
+ undefined,
4052
+ {},
4053
+ options,
4054
+ );
4055
+ }
4056
+
4057
+ /** List workspace-owned unpinned Vercel AI Gateway model slugs. */
4058
+ async listWorkspaceGatewayCustomModels(
4059
+ workspaceId: string,
4060
+ ): Promise<WorkspaceGatewayCustomModelsResponse> {
4061
+ return await this.requestJson<WorkspaceGatewayCustomModelsResponse>(
4062
+ "GET",
4063
+ `/v1/workspaces/${workspaceId}/gateway-custom-models`,
4064
+ );
4065
+ }
4066
+
4067
+ /** Add one exact upstream Vercel AI Gateway slug. */
4068
+ async createWorkspaceGatewayCustomModel(
4069
+ workspaceId: string,
4070
+ request: CreateWorkspaceGatewayCustomModelRequest,
4071
+ ): Promise<WorkspaceGatewayCustomModel> {
4072
+ return await this.requestJson<WorkspaceGatewayCustomModel>(
4073
+ "POST",
4074
+ `/v1/workspaces/${workspaceId}/gateway-custom-models`,
4075
+ request,
4076
+ );
4077
+ }
4078
+
4079
+ /** Remove one workspace custom Gateway model by its stable row id. */
4080
+ async deleteWorkspaceGatewayCustomModel(
4081
+ workspaceId: string,
4082
+ customModelId: string,
4083
+ request: DeleteWorkspaceGatewayCustomModelRequest,
4084
+ ): Promise<void> {
4085
+ if (
4086
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
4087
+ customModelId,
4088
+ )
4089
+ ) {
4090
+ throw new TypeError("customModelId must be a UUID");
4091
+ }
4092
+ await this.requestVoid(
4093
+ "DELETE",
4094
+ `/v1/workspaces/${workspaceId}/gateway-custom-models/${encodeURIComponent(customModelId)}`,
4095
+ request,
4096
+ );
4097
+ }
4098
+
4099
+ /** List workspace-owned unpinned OpenRouter model slugs. */
4100
+ async listWorkspaceOpenRouterCustomModels(
4101
+ workspaceId: string,
4102
+ ): Promise<WorkspaceOpenRouterCustomModelsResponse> {
4103
+ return await this.requestJson<WorkspaceOpenRouterCustomModelsResponse>(
4104
+ "GET",
4105
+ `/v1/workspaces/${workspaceId}/openrouter-custom-models`,
4106
+ );
4107
+ }
4108
+
4109
+ /** Add one exact upstream OpenRouter slug. */
4110
+ async createWorkspaceOpenRouterCustomModel(
4111
+ workspaceId: string,
4112
+ request: CreateWorkspaceOpenRouterCustomModelRequest,
4113
+ ): Promise<WorkspaceOpenRouterCustomModel> {
4114
+ return await this.requestJson<WorkspaceOpenRouterCustomModel>(
4115
+ "POST",
4116
+ `/v1/workspaces/${workspaceId}/openrouter-custom-models`,
4117
+ request,
4118
+ );
4119
+ }
4120
+
4121
+ /** Remove one workspace custom OpenRouter model by its stable row id. */
4122
+ async deleteWorkspaceOpenRouterCustomModel(
4123
+ workspaceId: string,
4124
+ customModelId: string,
4125
+ request: DeleteWorkspaceOpenRouterCustomModelRequest,
4126
+ ): Promise<void> {
4127
+ if (
4128
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
4129
+ customModelId,
4130
+ )
4131
+ ) {
4132
+ throw new TypeError("customModelId must be a UUID");
4133
+ }
4134
+ await this.requestVoid(
4135
+ "DELETE",
4136
+ `/v1/workspaces/${workspaceId}/openrouter-custom-models/${encodeURIComponent(customModelId)}`,
4137
+ request,
3889
4138
  );
3890
4139
  }
3891
4140
 
@@ -3912,10 +4161,14 @@ export class OpenGeniClient {
3912
4161
  /** Authenticated realtime voice models and credential readiness. */
3913
4162
  async getWorkspaceRealtimeModelCatalog(
3914
4163
  workspaceId: string,
4164
+ options: OpenGeniRequestOptions = {},
3915
4165
  ): Promise<WorkspaceRealtimeModelCatalogResponse> {
3916
4166
  return await this.requestJson<WorkspaceRealtimeModelCatalogResponse>(
3917
4167
  "GET",
3918
4168
  `/v1/workspaces/${workspaceId}/realtime-model-catalog`,
4169
+ undefined,
4170
+ {},
4171
+ options,
3919
4172
  );
3920
4173
  }
3921
4174
 
@@ -4271,6 +4524,18 @@ export class OpenGeniClient {
4271
4524
  return await this.requestJson<Workspace>("POST", "/v1/workspaces", request);
4272
4525
  }
4273
4526
 
4527
+ /**
4528
+ * Create an organization workspace once for a stable external tenant
4529
+ * identity, or return the existing workspace without overwriting it.
4530
+ */
4531
+ async ensureWorkspace(request: EnsureWorkspaceRequest): Promise<EnsureWorkspaceResponse> {
4532
+ return await this.requestJson<EnsureWorkspaceResponse>(
4533
+ "PUT",
4534
+ "/v1/workspaces/external",
4535
+ request,
4536
+ );
4537
+ }
4538
+
4274
4539
  async getWorkspace(workspaceId: string): Promise<Workspace> {
4275
4540
  return await this.requestJson<Workspace>("GET", `/v1/workspaces/${workspaceId}`);
4276
4541
  }
@@ -4531,6 +4796,24 @@ export class OpenGeniClient {
4531
4796
  );
4532
4797
  }
4533
4798
 
4799
+ async getCompanyProfileAgentPolicy(workspaceId: string): Promise<CompanyProfileAgentPolicy> {
4800
+ return await this.requestJson<CompanyProfileAgentPolicy>(
4801
+ "GET",
4802
+ `/v1/workspaces/${workspaceId}/company-profile/agent-policy`,
4803
+ );
4804
+ }
4805
+
4806
+ async updateCompanyProfileAgentPolicy(
4807
+ workspaceId: string,
4808
+ request: UpdateCompanyProfileAgentPolicyRequest,
4809
+ ): Promise<CompanyProfileAgentPolicy> {
4810
+ return await this.requestJson<CompanyProfileAgentPolicy>(
4811
+ "PATCH",
4812
+ `/v1/workspaces/${workspaceId}/company-profile/agent-policy`,
4813
+ request,
4814
+ );
4815
+ }
4816
+
4534
4817
  async updateCompanyProfile(
4535
4818
  workspaceId: string,
4536
4819
  request: UpdateCompanyProfileRequest,
@@ -4721,9 +5004,17 @@ export class OpenGeniClient {
4721
5004
  return response.members;
4722
5005
  }
4723
5006
 
5007
+ /** Active organization members who can be added to this workspace. */
5008
+ async listWorkspaceMemberCandidates(workspaceId: string): Promise<WorkspaceMemberCandidate[]> {
5009
+ const response = await this.requestJson<ListWorkspaceMemberCandidatesResponse>(
5010
+ "GET",
5011
+ `/v1/workspaces/${workspaceId}/member-candidates`,
5012
+ );
5013
+ return response.members;
5014
+ }
5015
+
4724
5016
  /**
4725
- * Add an already-registered user by email. 404s when no user with that email
4726
- * exists (email invites for unknown users are deferred).
5017
+ * Add one active member selected from the workspace candidate inventory.
4727
5018
  */
4728
5019
  async addWorkspaceMember(
4729
5020
  workspaceId: string,
@@ -4848,7 +5139,7 @@ export class OpenGeniClient {
4848
5139
  return await this.requestJson<ScheduledTask>(
4849
5140
  "POST",
4850
5141
  `/v1/workspaces/${workspaceId}/scheduled-tasks`,
4851
- request,
5142
+ normalizeScheduledTaskMachineTarget(request),
4852
5143
  );
4853
5144
  }
4854
5145
 
@@ -4860,7 +5151,7 @@ export class OpenGeniClient {
4860
5151
  return await this.requestJson<ScheduledTask>(
4861
5152
  "PATCH",
4862
5153
  `/v1/workspaces/${workspaceId}/scheduled-tasks/${taskId}`,
4863
- request,
5154
+ normalizeScheduledTaskMachineTarget(request),
4864
5155
  );
4865
5156
  }
4866
5157
 
@@ -5280,6 +5571,7 @@ export class OpenGeniClient {
5280
5571
  ) {
5281
5572
  throw new Error("File upload timeout must be a positive number");
5282
5573
  }
5574
+ assertBrowserFileUploadSecureContext();
5283
5575
  const withTimeout = async <T>(
5284
5576
  timeoutMs: number,
5285
5577
  operation: (signal: AbortSignal) => Promise<T>,
@@ -6755,6 +7047,34 @@ export class OpenGeniClient {
6755
7047
  );
6756
7048
  }
6757
7049
 
7050
+ async listOrganizationApiKeys(organizationId: string): Promise<ApiKey[]> {
7051
+ const response = await this.requestJson<ListApiKeysResponse>(
7052
+ "GET",
7053
+ `/v1/organizations/${organizationId}/api-keys`,
7054
+ );
7055
+ return response.apiKeys;
7056
+ }
7057
+
7058
+ /** The returned `token` is shown once; only its prefix is stored. */
7059
+ async createOrganizationApiKey(
7060
+ organizationId: string,
7061
+ request: CreateOrganizationApiKeyRequest,
7062
+ ): Promise<CreateApiKeyResponse> {
7063
+ return await this.requestJson<CreateApiKeyResponse>(
7064
+ "POST",
7065
+ `/v1/organizations/${organizationId}/api-keys`,
7066
+ request,
7067
+ );
7068
+ }
7069
+
7070
+ /** Revoke an organization-scoped API key. Returns the revoked key. */
7071
+ async deleteOrganizationApiKey(organizationId: string, apiKeyId: string): Promise<ApiKey> {
7072
+ return await this.requestJson<ApiKey>(
7073
+ "DELETE",
7074
+ `/v1/organizations/${organizationId}/api-keys/${apiKeyId}`,
7075
+ );
7076
+ }
7077
+
6758
7078
  // --- Billing (account-scoped) --------------------------------------------------------------------
6759
7079
 
6760
7080
  async getBilling(options: { accountId?: string } = {}): Promise<BillingSummary> {
@@ -7105,14 +7425,20 @@ export class OpenGeniClient {
7105
7425
  }
7106
7426
 
7107
7427
  /** Contract-checked JSON transport shared by opt-in typed SDK clients. */
7108
- private async requestSessionCommand<T>(method: string, path: string, body?: unknown): Promise<T> {
7428
+ private async requestSessionCommand<T>(
7429
+ method: string,
7430
+ path: string,
7431
+ body?: unknown,
7432
+ options: OpenGeniRequestOptions = {},
7433
+ ): Promise<T> {
7109
7434
  return await this.requestJson<T>(
7110
7435
  method,
7111
7436
  path,
7112
7437
  body,
7113
7438
  {},
7114
7439
  {
7115
- timeoutMs: this.sessionCommandTimeoutMs,
7440
+ signal: options.signal,
7441
+ timeoutMs: options.timeoutMs ?? this.sessionCommandTimeoutMs,
7116
7442
  },
7117
7443
  );
7118
7444
  }
@@ -7469,6 +7795,28 @@ function mutationTransportError(correlationId: string): OpenGeniApiError {
7469
7795
  });
7470
7796
  }
7471
7797
 
7798
+ function assertBrowserFileUploadSecureContext(): void {
7799
+ const secureContext =
7800
+ typeof window !== "undefined"
7801
+ ? window.isSecureContext
7802
+ : typeof globalThis.isSecureContext === "boolean"
7803
+ ? globalThis.isSecureContext
7804
+ : undefined;
7805
+ // The framework-agnostic SDK also runs in Node, Bun, Deno, and edge
7806
+ // runtimes. Preserve their existing upload behavior; this typed HTTPS error
7807
+ // is only meaningful where the browser exposes a secure-context state.
7808
+ if (secureContext === undefined) return;
7809
+ if (!secureContext) {
7810
+ throw new OpenGeniSecureContextRequiredError("insecure_context");
7811
+ }
7812
+ if (
7813
+ typeof globalThis.crypto === "undefined" ||
7814
+ typeof globalThis.crypto.subtle?.digest !== "function"
7815
+ ) {
7816
+ throw new OpenGeniSecureContextRequiredError("web_crypto_unavailable");
7817
+ }
7818
+ }
7819
+
7472
7820
  async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<string> {
7473
7821
  const bytes =
7474
7822
  typeof body === "string"
@@ -7486,6 +7834,30 @@ async function sha256Hex(bytes: Uint8Array): Promise<string> {
7486
7834
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
7487
7835
  }
7488
7836
 
7837
+ type ComputerFrameEvidenceMismatchReason =
7838
+ | "frame_session_mismatch"
7839
+ | "frame_target_mismatch"
7840
+ | "frame_media_mismatch"
7841
+ | "frame_digest_mismatch";
7842
+
7843
+ function computerFrameEvidenceMismatchReason(
7844
+ metadata: ReturnType<typeof decodeComputerFrameMetadataHeader>,
7845
+ expected: {
7846
+ computerSessionId: string;
7847
+ targetId: string;
7848
+ mediaType: "image/jpeg" | "image/png";
7849
+ sha256: string;
7850
+ },
7851
+ ): ComputerFrameEvidenceMismatchReason | null {
7852
+ if (metadata.computerSessionId !== expected.computerSessionId) {
7853
+ return "frame_session_mismatch";
7854
+ }
7855
+ if (metadata.targetId !== expected.targetId) return "frame_target_mismatch";
7856
+ if (metadata.mediaType !== expected.mediaType) return "frame_media_mismatch";
7857
+ if (metadata.sha256 !== expected.sha256) return "frame_digest_mismatch";
7858
+ return null;
7859
+ }
7860
+
7489
7861
  async function cancelResponseBody(response: Response, reason: string): Promise<void> {
7490
7862
  await response.body?.cancel(reason).catch(() => undefined);
7491
7863
  }