@opengeni/sdk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -11,8 +11,9 @@ export type SessionStatus =
11
11
  | "failed"
12
12
  | "cancelled";
13
13
 
14
- // Mirror of `@opengeni/contracts` SandboxBackend (10 values; existing four keep
15
- // position). 3-way enum parity is pinned by `test/contract-parity.test.ts`.
14
+ // Mirror of `@opengeni/contracts` SandboxBackend (11 values; every member is
15
+ // additive at the end). 3-way enum parity is pinned by
16
+ // `test/contract-parity.test.ts`.
16
17
  export type SandboxBackend =
17
18
  | "docker"
18
19
  | "modal"
@@ -23,7 +24,8 @@ export type SandboxBackend =
23
24
  | "e2b"
24
25
  | "blaxel"
25
26
  | "cloudflare"
26
- | "vercel";
27
+ | "vercel"
28
+ | "selfhosted";
27
29
 
28
30
  // Mirror of `@opengeni/contracts` SandboxOs. Only "linux" is reachable in v1.
29
31
  export type SandboxOs = "linux" | "macos" | "windows";
@@ -43,7 +45,12 @@ export type CapabilityUnavailableReason =
43
45
  | "not_provisioned"
44
46
  | "disabled_by_policy"
45
47
  | "lease_cold"
46
- | "tier_headless";
48
+ | "tier_headless"
49
+ // selfhosted (bring-your-own-compute) negotiation states:
50
+ | "agent_offline"
51
+ | "agent_reconnecting"
52
+ | "consent_required"
53
+ | "display_unavailable";
47
54
 
48
55
  // Mirror of `@opengeni/contracts` SessionCapabilities (the negotiated handshake
49
56
  // document). The descriptor table itself is NOT mirrored — it lives in
@@ -77,8 +84,10 @@ export type SessionCapabilities = {
77
84
  reason: CapabilityUnavailableReason | null;
78
85
  };
79
86
  DesktopStream: {
80
- transport: "vnc-ws" | "rdp-ws" | "webrtc" | null;
81
- client: "novnc" | "web-rdp" | null;
87
+ // "relay-frames" + "frames": the selfhosted framebuffer stream — PNG-per-frame
88
+ // protobuf datagrams over the relay, painted by a canvas client (NOT RFB).
89
+ transport: "vnc-ws" | "rdp-ws" | "webrtc" | "relay-frames" | null;
90
+ client: "novnc" | "web-rdp" | "frames" | null;
82
91
  mode: "read-only" | "interactive";
83
92
  url: string | null;
84
93
  token: string | null;
@@ -249,6 +258,10 @@ export type Session = {
249
258
  temporalWorkflowId: string | null;
250
259
  activeTurnId: string | null;
251
260
  lastSequence: number;
261
+ /** Multi-account Codex (P1): the account this session is pinned to (null ⇒ follow workspace active). */
262
+ codexPinnedCredentialId?: string | null;
263
+ /** Multi-account Codex (P1): the account the most recent turn ran on (the "Running on:" indicator). */
264
+ codexLastCredentialId?: string | null;
252
265
  createdAt: string;
253
266
  updatedAt: string;
254
267
  };
@@ -336,6 +349,8 @@ export const SESSION_EVENT_TYPES = [
336
349
  "terminal.pty.output.delta",
337
350
  "terminal.pty.exited",
338
351
  "session.title_set",
352
+ // Multi-account Codex (P1): the session's inference account changed.
353
+ "codex.account.switched",
339
354
  ] as const;
340
355
 
341
356
  export type KnownSessionEventType = (typeof SESSION_EVENT_TYPES)[number];
@@ -578,6 +593,12 @@ export type CreateSessionRequest = {
578
593
  model?: string | undefined;
579
594
  reasoningEffort?: ReasoningEffort | undefined;
580
595
  sandboxBackend?: SandboxBackend | undefined;
596
+ // The enrolled machine (a sandbox id) to run this session on; seeds the
597
+ // active-sandbox pointer at creation so the first turn lands on it.
598
+ targetSandboxId?: string | undefined;
599
+ // Host working directory for a connected-machine target (the agent runs here;
600
+ // default = the machine's launch dir). Ignored for managed sandboxes.
601
+ workingDir?: string | undefined;
581
602
  environmentId?: string | undefined;
582
603
  goal?: GoalSpec | undefined;
583
604
  clientEventId?: string | undefined;
@@ -586,6 +607,13 @@ export type CreateSessionRequest = {
586
607
  // Distinct from the per-call clientEventId.
587
608
  idempotencyKey?: string | undefined;
588
609
  firstPartyMcpPermissions?: string[] | undefined;
610
+ // Shared-sandbox placement (mirror of `@opengeni/contracts` CreateSessionRequest.sandbox,
611
+ // addendum 05 §D.1). Three-way union; OMITTED ⇒ the context-dependent server default
612
+ // (from inside a session → "shared" with the creator's box, top-level → "new").
613
+ // - "shared": join the CREATOR's box (requires a parent session; top-level → 422).
614
+ // - "new": mint a fresh singleton box (group ≡ the new session's id).
615
+ // - {groupId}: join a SPECIFIC sibling group in THIS workspace (manager fan-out).
616
+ sandbox?: "shared" | "new" | { groupId: string } | undefined;
589
617
  };
590
618
 
591
619
  // --- Access, workspaces, API keys -------------------------------------------
@@ -623,6 +651,8 @@ export const KNOWN_PERMISSIONS = [
623
651
  "environments:manage",
624
652
  "environments:use",
625
653
  "goals:manage",
654
+ "enrollments:read",
655
+ "enrollments:manage",
626
656
  ] as const;
627
657
 
628
658
  export type KnownPermission = (typeof KNOWN_PERMISSIONS)[number];
@@ -651,6 +681,124 @@ export type ClientModel = {
651
681
  contextWindowTokens?: number | undefined;
652
682
  };
653
683
 
684
+ /**
685
+ * Connection state of a workspace's Codex (ChatGPT) subscription, returned by
686
+ * `GET /v1/workspaces/:id/codex/status`. `models` are the codex models the
687
+ * workspace can select (projected as ClientModel under their own "no credits"
688
+ * provider group), present only while connected.
689
+ */
690
+ export type CodexConnectionStatus = {
691
+ connected: boolean;
692
+ plan?: string | null;
693
+ valid?: boolean;
694
+ expiresAt?: string | null;
695
+ lastError?: string | null;
696
+ models?: ClientModel[];
697
+ /** The account a session runs on when unpinned (label for the in-session indicator). */
698
+ activeAccount?: { id: string; label?: string | null; chatgptAccountId?: string | null } | null;
699
+ /** How many Codex accounts the workspace has connected. */
700
+ accountCount?: number;
701
+ };
702
+
703
+ /**
704
+ * One normalized Codex usage window (5h or weekly), camelCase end-to-end (the
705
+ * route normalizes server-side; the web layer never re-hand-types snake_case).
706
+ * `percent` is authoritative; used/limit/remaining are a synthesized 0–100 scale
707
+ * (limit = 100) because the provider gives only a percentage. `remaining =
708
+ * 100 - percent` is the P3 rotation key. Identify the window by `limitWindowSeconds`
709
+ * (18000 ⇒ 5h, 604800 ⇒ weekly), never by position.
710
+ */
711
+ export type CodexUsageWindow = {
712
+ used: number;
713
+ limit: number;
714
+ remaining: number;
715
+ percent: number;
716
+ resetAt: string | null;
717
+ resetAfterSeconds: number | null;
718
+ limitWindowSeconds: number;
719
+ };
720
+
721
+ /** The normalized usage payload for one account — the P2/P3 contract. */
722
+ export type CodexUsagePayload = {
723
+ status: "ok" | "limit_reached" | "error" | "no-data";
724
+ planType: string | null;
725
+ fiveHour: CodexUsageWindow | null;
726
+ weekly: CodexUsageWindow | null;
727
+ limitReached: boolean;
728
+ fetchedAt: string;
729
+ /** Present only on an auth/refresh failure path. */
730
+ reason?: "needs_relogin";
731
+ additionalLimits?: Array<{ limitName: string; meteredFeature: string; fiveHour: CodexUsageWindow | null; weekly: CodexUsageWindow | null }>;
732
+ credits?: { hasCredits: boolean; unlimited: boolean; overageLimitReached: boolean; balance: string };
733
+ };
734
+
735
+ /** One connected Codex (ChatGPT) account in a workspace (multi-account P1). Metadata only. */
736
+ export type CodexAccount = {
737
+ id: string;
738
+ chatgptAccountId?: string | null;
739
+ label?: string | null;
740
+ email?: string | null;
741
+ plan?: string | null;
742
+ status: "active" | "needs_relogin" | "error";
743
+ active: boolean;
744
+ expiresAt?: string | null;
745
+ lastRefreshAt?: string | null;
746
+ lastError?: string | null;
747
+ // P2 CACHED usage (built from the persisted columns; renders bars off
748
+ // listCodexAccounts with no second call). null until the first live refresh.
749
+ fiveHour?: CodexUsageWindow | null;
750
+ weekly?: CodexUsageWindow | null;
751
+ usageCheckedAt?: string | null;
752
+ // P3 rotation cooldown: ISO timestamp until which this account is cooling-down
753
+ // (rotated-off after a usage cap). null/absent ⇒ not cooling.
754
+ exhaustedUntil?: string | null;
755
+ };
756
+
757
+ /** Per-workspace Codex rotation/active settings. P1: rotation inert, only activeCredentialId loads. */
758
+ export type CodexRotationSettings = {
759
+ rotationEnabled: boolean;
760
+ rotationStrategy: "most_remaining" | "round_robin" | "drain_then_next";
761
+ activeCredentialId: string | null;
762
+ };
763
+
764
+ /** GET /codex/accounts — the accounts list + the workspace active pointer + settings. */
765
+ export type CodexAccountsResponse = {
766
+ accounts: CodexAccount[];
767
+ activeAccountId: string | null;
768
+ settings: CodexRotationSettings;
769
+ };
770
+
771
+ /** Payload of a `codex.account.switched` session event. */
772
+ export type CodexAccountSwitchedPayload = {
773
+ fromAccountId: string | null;
774
+ toAccountId: string;
775
+ reason: "manual" | "exhausted" | "rotation";
776
+ // P4 connector-aware rotation: the session's used connectors that the new account
777
+ // does NOT cover (a prefer-not-require failover that dropped a connector). Present
778
+ // only on such a switch; the UI renders a "dropped <connector>" badge on the pill.
779
+ droppedConnectors?: string[];
780
+ };
781
+
782
+ /** Device-code start: show `userCode` at `verificationUri`, then poll with `state`. */
783
+ export type CodexConnectStart = {
784
+ userCode: string;
785
+ verificationUri: string;
786
+ intervalSeconds: number;
787
+ state: string;
788
+ };
789
+
790
+ /** Poll result: keep polling on `pending`, restart on `expired`, done on `connected`. */
791
+ export type CodexConnectPoll =
792
+ | { status: "pending" }
793
+ | { status: "expired" }
794
+ | { status: "connected"; plan?: string | null; accountId?: string; isActive?: boolean };
795
+
796
+ /** Remaining usage/limits for one account. `usage` is the normalized P2 payload. */
797
+ export type CodexUsage = { status: "ok" | "limit_reached" | "error" | "no-data"; usage: CodexUsagePayload | null };
798
+
799
+ /** Batched live-refresh response, keyed by credential id; each entry independently statused. */
800
+ export type CodexUsageMap = Record<string, CodexUsage>;
801
+
654
802
  /**
655
803
  * How a deployment expects clients to authenticate to it, surfaced so a UI can
656
804
  * wire up the right header/cookie without prior knowledge of the host setup.
@@ -1472,3 +1620,187 @@ export type ClientSessionEventInput =
1472
1620
  | UserMessageEventInput
1473
1621
  | UserInterruptEventInput
1474
1622
  | UserApprovalDecisionEventInput;
1623
+
1624
+ // ── Bring-your-own-compute: Machines dashboard + per-machine metrics (M10) ────
1625
+ // Hand-written mirrors of the `@opengeni/contracts` MetricSample / MachineView /
1626
+ // MachinesResponse / MachineMetricsSeriesResponse (pinned by contract-parity).
1627
+ // M9 imports THESE so the dashboard UI never drifts from the API.
1628
+
1629
+ /** A point-in-time machine metrics sample. `gpuUtilPct`/`gpuMemBytes` are null
1630
+ * when no GPU was present (not-reported, never a real zero); the bytes/load are
1631
+ * numbers; `sampledAt` is an ISO-8601 instant. */
1632
+ export type MetricSample = {
1633
+ cpuPct: number;
1634
+ load1: number;
1635
+ load5: number;
1636
+ load15: number;
1637
+ memUsedBytes: number;
1638
+ memTotalBytes: number;
1639
+ diskUsedBytes: number;
1640
+ diskTotalBytes: number;
1641
+ gpuUtilPct: number | null;
1642
+ gpuMemBytes: number | null;
1643
+ runQueue: number;
1644
+ sampledAt: string;
1645
+ };
1646
+
1647
+ /** The derived dashboard state of a machine (M3 liveness + consent/display
1648
+ * reasons + the in-flight device-flow). */
1649
+ export type MachineState =
1650
+ | "online"
1651
+ | "reconnecting"
1652
+ | "offline"
1653
+ | "consent_required"
1654
+ | "display_unavailable"
1655
+ | "enrolling";
1656
+
1657
+ export type MachineKind = "modal" | "selfhosted";
1658
+
1659
+ /** A machine as the Machines dashboard renders it (an enrolled selfhosted machine
1660
+ * or the session's synthetic Modal group box, `isSessionGroup: true`). */
1661
+ export type MachineView = {
1662
+ sandboxId: string;
1663
+ enrollmentId: string | null;
1664
+ name: string;
1665
+ kind: MachineKind;
1666
+ state: MachineState;
1667
+ active: boolean;
1668
+ isSessionGroup: boolean;
1669
+ os: string;
1670
+ arch: string;
1671
+ hasDisplay: boolean;
1672
+ allowScreenControl: boolean;
1673
+ sharedSessionCount: number;
1674
+ lastSeenAt: string | null;
1675
+ metrics: MetricSample | null;
1676
+ };
1677
+
1678
+ /** GET /v1/workspaces/:ws/machines — the dashboard list + the active-sandbox
1679
+ * pointer (null activeSandboxId == the session's own group box is active). */
1680
+ export type MachinesResponse = {
1681
+ activeSandboxId: string | null;
1682
+ activeEpoch: number;
1683
+ machines: MachineView[];
1684
+ };
1685
+
1686
+ /** GET /v1/workspaces/:ws/machines/:enrollmentId/metrics/series — the downsampled
1687
+ * (~1/min) history the dashboard time-range reads. */
1688
+ export type MachineMetricsSeriesResponse = {
1689
+ samples: MetricSample[];
1690
+ };
1691
+
1692
+ /** POST /v1/workspaces/:ws/sessions/:sessionId/active-sandbox — swap a session's
1693
+ * active sandbox. `target` is a `MachineView.sandboxId`, or "session"/"default"
1694
+ * to swap back to the session's own group box. */
1695
+ export type SwapActiveSandboxRequest = {
1696
+ target: string;
1697
+ };
1698
+
1699
+ /** The swap outcome (mirrors the server `FleetSwapResult`). `swapped` is true on a
1700
+ * successful repoint OR a no-op (already there); `reason` carries the failure
1701
+ * detail (unowned/offline target, or a lost epoch fence) when false. */
1702
+ export type SwapActiveSandboxResponse = {
1703
+ swapped: boolean;
1704
+ activeSandboxId: string | null;
1705
+ activeEpoch: number;
1706
+ reason?: string;
1707
+ };
1708
+
1709
+ // ── Self-hosted enrollment UX (design 11) ────────────────────────────────────
1710
+ // Hand-written mirrors of the `@opengeni/contracts` enrollment-UX request/response
1711
+ // shapes (the SDK keeps zero runtime deps). The click-Grant approve-page
1712
+ // lookup/deny + the headless enroll-token mint/exchange.
1713
+
1714
+ /** Mirror of `@opengeni/contracts` EnrollmentOs. */
1715
+ export type EnrollmentOs = "linux" | "macos" | "windows";
1716
+
1717
+ /** POST /v1/enrollments/device/lookup body. */
1718
+ export type DeviceEnrollmentLookupRequest = {
1719
+ userCode: string;
1720
+ };
1721
+
1722
+ /** The presentational machine details the consent screen renders. */
1723
+ export type DeviceEnrollmentLookupMachine = {
1724
+ machineName: string | null;
1725
+ os: EnrollmentOs;
1726
+ arch: string;
1727
+ canOfferDisplay: boolean;
1728
+ requestsScreenControl: boolean;
1729
+ };
1730
+
1731
+ /** POST /v1/enrollments/device/lookup response (no secrets, no device_code). */
1732
+ export type DeviceEnrollmentLookupResponse = {
1733
+ workspaceId: string;
1734
+ userCode: string;
1735
+ machine: DeviceEnrollmentLookupMachine;
1736
+ expiresAt: string;
1737
+ };
1738
+
1739
+ /** POST /v1/workspaces/:ws/enrollments/device/approve body. */
1740
+ export type DeviceEnrollmentApproveRequest = {
1741
+ userCode: string;
1742
+ allowScreenControl?: boolean;
1743
+ };
1744
+
1745
+ /** POST /v1/workspaces/:ws/enrollments/device/approve response. */
1746
+ export type DeviceEnrollmentApproveResponse = {
1747
+ approved: boolean;
1748
+ enrollmentId: string;
1749
+ sandboxId: string;
1750
+ allowScreenControl: boolean;
1751
+ };
1752
+
1753
+ /** POST /v1/workspaces/:ws/enrollments/device/deny body. */
1754
+ export type DeviceEnrollmentDenyRequest = {
1755
+ userCode: string;
1756
+ };
1757
+
1758
+ /** POST /v1/workspaces/:ws/enrollments/device/deny response. */
1759
+ export type DeviceEnrollmentDenyResponse = {
1760
+ denied: boolean;
1761
+ };
1762
+
1763
+ /** POST /v1/workspaces/:ws/enrollments/token body. */
1764
+ export type MintEnrollTokenRequest = {
1765
+ allowScreenControl?: boolean;
1766
+ };
1767
+
1768
+ /** POST /v1/workspaces/:ws/enrollments/token response. The `token` is SECRET. */
1769
+ export type MintEnrollTokenResponse = {
1770
+ token: string;
1771
+ expiresAt: string;
1772
+ expiresInSeconds: number;
1773
+ };
1774
+
1775
+ /** The credential payload the headless exchange returns (a subset of the agent's
1776
+ * EnrollmentCredentials — IDENTICAL to the device-flow poll authorized branch). */
1777
+ export type EnrollmentCredentials = {
1778
+ agentId: string;
1779
+ workspaceId: string;
1780
+ bearer: string;
1781
+ subjectPrefix: string;
1782
+ natsUrls: string[];
1783
+ relayUrl: string;
1784
+ relayToken: string;
1785
+ natsAccountCreds: string;
1786
+ updatePublicKey: string;
1787
+ consentedWholeMachine: boolean;
1788
+ consentedScreenControl: boolean;
1789
+ };
1790
+
1791
+ /** POST /v1/enrollments/token/exchange body (the headless / fleet enroll path). */
1792
+ export type EnrollTokenExchangeRequest = {
1793
+ token: string;
1794
+ publicKey: string;
1795
+ os?: EnrollmentOs;
1796
+ arch?: string;
1797
+ machineName?: string;
1798
+ exposure?: "whole-machine";
1799
+ canOfferDisplay?: boolean;
1800
+ requestsScreenControl?: boolean;
1801
+ };
1802
+
1803
+ /** POST /v1/enrollments/token/exchange response (wraps the credential shape). */
1804
+ export type EnrollTokenExchangeResponse = {
1805
+ credentials: EnrollmentCredentials;
1806
+ };