@opengeni/sdk 0.3.1 → 0.5.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,7 +11,193 @@ export type SessionStatus =
11
11
  | "failed"
12
12
  | "cancelled";
13
13
 
14
- export type SandboxBackend = "docker" | "modal" | "local" | "none";
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`.
16
+ export type SandboxBackend =
17
+ | "docker"
18
+ | "modal"
19
+ | "local"
20
+ | "none"
21
+ | "daytona"
22
+ | "runloop"
23
+ | "e2b"
24
+ | "blaxel"
25
+ | "cloudflare"
26
+ | "vercel";
27
+
28
+ // Mirror of `@opengeni/contracts` SandboxOs. Only "linux" is reachable in v1.
29
+ export type SandboxOs = "linux" | "macos" | "windows";
30
+
31
+ // Mirror of `@opengeni/contracts` SandboxCapabilityName.
32
+ export type SandboxCapabilityName =
33
+ | "FileSystem"
34
+ | "Terminal"
35
+ | "Git"
36
+ | "DesktopStream"
37
+ | "Recording";
38
+
39
+ // Mirror of `@opengeni/contracts` CapabilityUnavailableReason.
40
+ export type CapabilityUnavailableReason =
41
+ | "backend_unsupported"
42
+ | "os_unsupported"
43
+ | "not_provisioned"
44
+ | "disabled_by_policy"
45
+ | "lease_cold"
46
+ | "tier_headless";
47
+
48
+ // Mirror of `@opengeni/contracts` SessionCapabilities (the negotiated handshake
49
+ // document). The descriptor table itself is NOT mirrored — it lives in
50
+ // contracts (P0.1) and is consumed by the SDK config in a later PR.
51
+ export type SessionCapabilities = {
52
+ sessionId: string;
53
+ backend: SandboxBackend;
54
+ os: SandboxOs;
55
+ liveness: "cold" | "warming" | "warm" | "draining";
56
+ leaseEpoch: number;
57
+ viewerHeartbeatIntervalMs: number;
58
+ FileSystem: {
59
+ available: boolean;
60
+ readOnly: boolean;
61
+ root: string;
62
+ pathSep: "/" | "\\";
63
+ treeMode: "lazy" | "snapshot";
64
+ reason: CapabilityUnavailableReason | null;
65
+ };
66
+ Terminal: {
67
+ transport: "sse-events" | "pty-ws" | null;
68
+ ptyCapable: boolean;
69
+ shell: string;
70
+ url: string | null;
71
+ token: string | null;
72
+ reason: CapabilityUnavailableReason | null;
73
+ };
74
+ Git: {
75
+ available: boolean;
76
+ repos: string[];
77
+ reason: CapabilityUnavailableReason | null;
78
+ };
79
+ DesktopStream: {
80
+ transport: "vnc-ws" | "rdp-ws" | "webrtc" | null;
81
+ client: "novnc" | "web-rdp" | null;
82
+ mode: "read-only" | "interactive";
83
+ url: string | null;
84
+ token: string | null;
85
+ expiresAt: string | null;
86
+ resolution: [number, number];
87
+ unredacted: boolean;
88
+ requiresAcknowledgment: boolean;
89
+ acknowledged: boolean;
90
+ // Shared-exposure disclosure (addendum E.1): `shared` when the group has >1
91
+ // session; `sharedSessionIds` lists the OTHER sessions' ids ONLY (never their
92
+ // conversation/metadata).
93
+ shared: boolean;
94
+ sharedSessionIds: string[];
95
+ reason: CapabilityUnavailableReason | null;
96
+ };
97
+ Recording: {
98
+ available: boolean;
99
+ modes: ("manual" | "on-turn" | "on-verify")[];
100
+ codecs: ("h264-mp4" | "vp9-webm")[];
101
+ reason: CapabilityUnavailableReason | null;
102
+ };
103
+ ComputerUse: {
104
+ available: boolean;
105
+ readOnly: boolean;
106
+ reason: CapabilityUnavailableReason | null;
107
+ };
108
+ negotiatedAt: string;
109
+ };
110
+
111
+ // Convenience aliases for the per-surface cells of `SessionCapabilities`, so the
112
+ // client hooks/components can take a single cell without restating the inline
113
+ // shape. These are exact structural views of the cells above.
114
+ export type FileSystemCapability = SessionCapabilities["FileSystem"];
115
+ export type TerminalCapability = SessionCapabilities["Terminal"];
116
+ export type GitCapability = SessionCapabilities["Git"];
117
+ export type DesktopStreamCapability = SessionCapabilities["DesktopStream"];
118
+ export type RecordingCapability = SessionCapabilities["Recording"];
119
+ export type ComputerUseCapability = SessionCapabilities["ComputerUse"];
120
+
121
+ // ── Stream-surfacing client surface (Phase 5) ───────────────────────────────
122
+ // Mirrors of the contracts viewer-attach / acknowledge / heartbeat shapes that
123
+ // the capability-gated client (`@opengeni/react`) drives. The desktop pixel
124
+ // plane rides Channel B (direct-to-provider noVNC); the structured terminal/
125
+ // files/git surfaces ride Channel A (the existing event spine + the synchronous
126
+ // fs/git/terminal point queries above). These are TYPES only (the SDK keeps zero
127
+ // runtime deps); the contract-parity test pins them.
128
+
129
+ // Mirror of `@opengeni/contracts` StreamUrlRotatedPayload — the Channel-A event
130
+ // the client folds in to hot-swap its noVNC socket on a box rollover, fenced on
131
+ // leaseEpoch.
132
+ export type StreamUrlRotatedPayload = {
133
+ url: string;
134
+ token: string | null;
135
+ expiresAt: string | null;
136
+ leaseEpoch: number;
137
+ transport: "vnc-ws";
138
+ viewerId: string | null;
139
+ };
140
+ export type StreamOpenedPayload = { viewerId: string; shared: boolean; viewerCount: number };
141
+ export type StreamClosedPayload = {
142
+ viewerId: string;
143
+ reason: "client-disconnect" | "reaped" | "revoked" | "box-rollover";
144
+ viewerCount: number;
145
+ };
146
+ export type StreamRevokedPayload = {
147
+ viewerId: string | null;
148
+ reason: "grant-revoked" | "session-failed" | "admin";
149
+ };
150
+
151
+ // Mirror of `@opengeni/contracts` AttachViewerRequest. Omitting `viewerId` mints
152
+ // a fresh holder id (returned on the response, carried through heartbeat/detach).
153
+ // `desktop:true` opts into the un-redacted pixel plane (the consent-gated noVNC
154
+ // stream); a terminal/files-only warm attach omits it (defaults false) so it
155
+ // warms the box + mints the pty-ws terminal cell WITHOUT tripping the consent 409.
156
+ export type AttachViewerRequest = { viewerId?: string | undefined; desktop?: boolean | undefined };
157
+
158
+ // Mirror of `@opengeni/contracts` ViewerHolder + the P4.2 desktop-stream fields
159
+ // the POST /viewers handler folds in when the pixel plane is minted in-process.
160
+ export type ViewerHolder = {
161
+ viewerId: string;
162
+ sandboxGroupId: string;
163
+ liveness: "cold" | "warming" | "warm" | "draining";
164
+ leaseEpoch: number;
165
+ viewerHeartbeatIntervalMs: number;
166
+ dataPlaneUrl: string | null;
167
+ };
168
+ export type AttachViewerResponse = ViewerHolder & {
169
+ // The scoped desktop-stream address minted for THIS holder (P4.2). Null when
170
+ // the deployment is headless / desktop is disabled / the mint degraded —
171
+ // the client then falls back to the Channel-A surfaces only.
172
+ streamToken: string | null;
173
+ streamExpiresAt: string | null;
174
+ resolution: [number, number] | null;
175
+ transport: "vnc-ws" | null;
176
+ client: "novnc" | null;
177
+ // The scoped ttyd PTY-over-websocket address minted for THIS holder — the REAL
178
+ // interactive terminal, symmetric with the desktop pixel plane (same Modal
179
+ // tunnel, same scoped stream token). Populated on a warm box; null when the
180
+ // terminal mint degraded (headless / no secret / tunnel failure), in which case
181
+ // the client falls back to the Channel-A read-only command-output firehose.
182
+ // `terminalTransport` is "pty-ws" iff a live `terminalUrl` was minted.
183
+ terminalUrl: string | null;
184
+ terminalToken: string | null;
185
+ terminalTransport: "pty-ws" | null;
186
+ };
187
+
188
+ // Mirror of `@opengeni/contracts` AcknowledgeStreamRequest/Response — the
189
+ // un-redacted-pixel + shared-exposure consent gate (P3.2).
190
+ export type AcknowledgeStreamRequest = {
191
+ acknowledgeUnredacted?: boolean | undefined;
192
+ acknowledgeShared?: boolean | undefined;
193
+ };
194
+ export type AcknowledgeStreamResponse = { acknowledged: boolean; acknowledgedShared: boolean };
195
+
196
+ // Mirror of `@opengeni/contracts` ViewerHeartbeatRequest/Response — the
197
+ // Channel-A viewer-liveness ping, epoch-fenced (a stale-epoch beat → alive:false
198
+ // → the client re-attaches).
199
+ export type ViewerHeartbeatRequest = { leaseEpoch: number };
200
+ export type ViewerHeartbeatResponse = { alive: boolean };
15
201
 
16
202
  export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
17
203
 
@@ -50,6 +236,8 @@ export type Session = {
50
236
  accountId: string;
51
237
  status: SessionStatus;
52
238
  initialMessage: string;
239
+ title: string | null;
240
+ titleSource: "user" | "agent" | null;
53
241
  resources: ResourceRef[];
54
242
  tools: ToolRef[];
55
243
  metadata: Record<string, unknown>;
@@ -130,6 +318,24 @@ export const SESSION_EVENT_TYPES = [
130
318
  "goal.paused",
131
319
  "goal.resumed",
132
320
  "goal.continuation",
321
+ // Channel-B desktop pixel-plane signals (mirror of contracts SessionEventType;
322
+ // the contract-parity test asserts sorted equality).
323
+ "stream.url.rotated",
324
+ "stream.opened",
325
+ "stream.closed",
326
+ "stream.revoked",
327
+ // Channel-B recording signals (P4.3 — "agent films itself proving the fix").
328
+ "recording.started",
329
+ "recording.available",
330
+ "recording.failed",
331
+ // Channel-A structured-service notifications (P4.4; mirror of contracts
332
+ // SessionEventType — the contract-parity test asserts sorted equality).
333
+ "fs.changed",
334
+ "git.changed",
335
+ "terminal.pty.started",
336
+ "terminal.pty.output.delta",
337
+ "terminal.pty.exited",
338
+ "session.title_set",
133
339
  ] as const;
134
340
 
135
341
  export type KnownSessionEventType = (typeof SESSION_EVENT_TYPES)[number];
@@ -166,6 +372,144 @@ export type AgentToolCallCreatedPayload = {
166
372
  export type AgentToolCallOutputPayload = { id: string | null; output: unknown };
167
373
  export type SessionStatusChangedPayload = { status: SessionStatus };
168
374
 
375
+ // Recording payloads (P4.3 — plain TS mirror of the contracts Zod schemas; the
376
+ // SDK is zero-runtime-dep so these are TYPES, not Zod, F15). The contract-parity
377
+ // test asserts the event-type literals; these shapes document the wire payloads.
378
+ export type RecordingMode = "manual" | "on-turn" | "on-verify";
379
+ export type RecordingCodec = "h264-mp4" | "vp9-webm";
380
+ export type RecordingContentType = "video/mp4" | "video/webm";
381
+ export type RecordingFailedReason =
382
+ | "ffmpeg-error"
383
+ | "box-death"
384
+ | "box-rollover"
385
+ | "upload-failed"
386
+ | "max-bytes-exceeded"
387
+ | "display-unavailable";
388
+
389
+ export type RecordingStartedPayload = {
390
+ recordingId: string;
391
+ turnId: string | null;
392
+ mode: RecordingMode;
393
+ codec: RecordingCodec;
394
+ dimensions: [number, number];
395
+ framerate: number;
396
+ startedAt: string;
397
+ reason?: string | null | undefined;
398
+ };
399
+ export type RecordingAvailablePayload = {
400
+ recordingId: string;
401
+ turnId: string | null;
402
+ codec: RecordingCodec;
403
+ contentType: RecordingContentType;
404
+ storageKey: string;
405
+ durationSeconds: number | null;
406
+ sizeBytes: number;
407
+ dimensions: [number, number];
408
+ };
409
+ export type RecordingFailedPayload = {
410
+ recordingId: string;
411
+ turnId: string | null;
412
+ reason: RecordingFailedReason;
413
+ detail?: string | null | undefined;
414
+ };
415
+
416
+ // ── Channel-A structured services (P4.4) — hand-written wire mirrors ─────────
417
+
418
+ // A1 notification payloads.
419
+ export type SandboxCommandOutputDeltaPayload = {
420
+ stream: "stdout" | "stderr";
421
+ chunk: string;
422
+ commandId?: string | undefined;
423
+ seq?: number | undefined;
424
+ };
425
+ export type FsChangeKind = "created" | "modified" | "deleted" | "renamed";
426
+ export type FsChangedPayload = {
427
+ changes: { path: string; kind: FsChangeKind; isDir: boolean; sizeBytes: number | null; oldPath?: string | undefined }[];
428
+ source: "write" | "watch" | "agent";
429
+ revision: number;
430
+ leaseEpoch: number;
431
+ };
432
+ export type GitChangedPayload = {
433
+ head: string | null;
434
+ dirty: boolean;
435
+ ahead: number;
436
+ behind: number;
437
+ changedFileCount: number;
438
+ reason: "commit" | "checkout" | "stage" | "worktree" | "fetch" | "unknown";
439
+ revision: number;
440
+ leaseEpoch: number;
441
+ };
442
+ export type TerminalPtyStartedPayload = { ptyId: string; cols: number; rows: number; shell: string; cwd: string };
443
+ export type TerminalPtyOutputDeltaPayload = { ptyId: string; stream: "stdout" | "stderr"; chunk: string; seq: number };
444
+ export type TerminalPtyExitedPayload = { ptyId: string; exitCode: number | null; reason: "exit" | "killed" | "owner_gone" | "timeout" };
445
+
446
+ // A2 FileSystem request/response.
447
+ export type FsNodeType = "file" | "dir" | "symlink" | "other";
448
+ export type FsTreeNode = {
449
+ name: string;
450
+ path: string;
451
+ type: FsNodeType;
452
+ sizeBytes: number | null;
453
+ mtimeMs: number | null;
454
+ mode: number | null;
455
+ children?: FsTreeNode[] | undefined;
456
+ truncated: boolean;
457
+ };
458
+ export type FsEncoding = "utf8" | "base64";
459
+ export type FsListRequest = { path?: string; depth?: number; maxEntries?: number; includeHidden?: boolean };
460
+ export type FsListResponse = { root: FsTreeNode; revision: number; truncated: boolean };
461
+ export type FsReadRequest = { path: string; encoding?: FsEncoding; maxBytes?: number };
462
+ export type FsReadResponse = { path: string; encoding: FsEncoding; content: string; sizeBytes: number; truncated: boolean; isBinary: boolean; revision: number };
463
+ export type FsWriteRequest = { path: string; encoding?: FsEncoding; content: string; overwrite?: boolean; createParents?: boolean };
464
+ export type FsWriteResponse = { path: string; sizeBytes: number; revision: number };
465
+ export type FsDeleteRequest = { path: string; recursive?: boolean };
466
+ export type FsDeleteResponse = { revision: number };
467
+ export type FsMoveRequest = { path: string; newPath: string; overwrite?: boolean; createParents?: boolean };
468
+ export type FsMoveResponse = { path: string; newPath: string; revision: number };
469
+ export type FsMkdirRequest = { path: string; recursive?: boolean };
470
+ export type FsMkdirResponse = { path: string; revision: number };
471
+
472
+ // A2 Git request/response (the Pierre-diff feed).
473
+ export type GitFileStatusCode = "added" | "modified" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "conflicted" | "typechange";
474
+ export type GitFileStatus = { path: string; oldPath: string | null; index: GitFileStatusCode | null; worktree: GitFileStatusCode | null; isConflicted: boolean };
475
+ export type GitStatusRequest = { path?: string };
476
+ export type GitStatusResponse = { isRepo: boolean; head: string | null; detached: boolean; upstream: string | null; ahead: number; behind: number; files: GitFileStatus[]; revision: number };
477
+ export type GitDiffLineType = "context" | "add" | "del" | "meta";
478
+ export type GitDiffLine = { type: GitDiffLineType; oldNo: number | null; newNo: number | null; text: string };
479
+ export type GitDiffHunk = { oldStart: number; oldLines: number; newStart: number; newLines: number; header: string; lines: GitDiffLine[] };
480
+ export type GitFileDiff = { path: string; oldPath: string | null; status: GitFileStatusCode; isBinary: boolean; isImage: boolean; additions: number; deletions: number; hunks: GitDiffHunk[]; truncated: boolean };
481
+ export type GitDiffRequest = { path?: string; staged?: boolean; fromRef?: string; toRef?: string; pathspec?: string[]; contextLines?: number; maxBytesPerFile?: number };
482
+ export type GitDiffResponse = { files: GitFileDiff[]; revision: number };
483
+ export type GitLogRequest = { path?: string; ref?: string; maxCount?: number; skip?: number; pathspec?: string[] };
484
+ export type GitCommit = {
485
+ sha: string;
486
+ shortSha: string;
487
+ parents: string[];
488
+ author: { name: string; email: string; timestamp: number };
489
+ committer: { name: string; email: string; timestamp: number };
490
+ subject: string;
491
+ body: string;
492
+ refs: string[];
493
+ };
494
+ export type GitLogResponse = { commits: GitCommit[]; hasMore: boolean };
495
+ export type GitShowRequest = { path?: string; ref: string; filePath?: string; encoding?: FsEncoding; maxBytesPerFile?: number };
496
+ export type GitShowResponse = { commit: GitCommit | null; files: GitFileDiff[]; blob: { content: string; encoding: FsEncoding; sizeBytes: number; truncated: boolean } | null; revision: number };
497
+
498
+ // A2 Terminal exec + PTY.
499
+ export type TerminalExecRequest = { command: string; cwd?: string; timeoutMs?: number; emitStream?: boolean };
500
+ export type TerminalExecResponse = { stdout: string; stderr: string; exitCode: number | null; running: boolean; wallTimeSeconds: number };
501
+ export type PtyOpenRequest = { cols?: number; rows?: number; cwd?: string; shell?: string };
502
+ export type PtyOpenResponse = { ptyId: string; streamVia: "sse-events"; supportsInput: boolean };
503
+ export type PtyWriteRequest = { ptyId: string; data: string };
504
+ export type PtyResizeRequest = { ptyId: string; cols: number; rows: number };
505
+ export type PtyCloseRequest = { ptyId: string };
506
+
507
+ export type SessionStructuredCapabilities = {
508
+ FileSystem: { available: boolean; readOnly: boolean; root: string };
509
+ Terminal: { events: boolean; exec: boolean; pty: { available: boolean } };
510
+ Git: { available: boolean; repos: string[] };
511
+ };
512
+
169
513
  export type ScheduledTaskStatus = "active" | "paused";
170
514
 
171
515
  export type ScheduledTaskRunMode = "new_session_per_run" | "reusable_session";
@@ -258,8 +602,17 @@ export const KNOWN_PERMISSIONS = [
258
602
  "sessions:create",
259
603
  "sessions:read",
260
604
  "sessions:control",
605
+ // Sandbox-surfacing (mirror of @opengeni/contracts Permission). stream:view is
606
+ // strictly broader than sessions:read (un-redacted pixels); stream:control is
607
+ // the never-granted-v1 raw-input plane; stream:acknowledge is the secret-leak
608
+ // consent gate.
609
+ "stream:view",
610
+ "stream:control",
611
+ "stream:acknowledge",
261
612
  "files:upload",
262
613
  "files:read",
614
+ "files:write",
615
+ "terminal:attach",
263
616
  "documents:manage",
264
617
  "documents:search",
265
618
  "scheduled_tasks:manage",
@@ -327,6 +680,11 @@ export type ClientConfig = {
327
680
  fileUploads: { enabled: boolean; maxSizeBytes: number };
328
681
  productAccessMode: ProductAccessMode;
329
682
  auth: ClientAuthConfig;
683
+ // Server-wide hint: does this deployment support Channel-A structured services
684
+ // at all (P4.4). Per-session availability is negotiated on /stream-capabilities;
685
+ // this is the coarse on/off the client uses to decide whether to even attempt
686
+ // the fs/git/terminal panels.
687
+ structuredServices: { fileSystem: boolean; git: boolean; terminalEvents: boolean };
330
688
  };
331
689
 
332
690
  export type AccountRole = "owner" | "admin" | "member";
@@ -474,6 +832,10 @@ export type UpdateSessionGoalRequest = {
474
832
  rationale?: string | undefined;
475
833
  };
476
834
 
835
+ export type UpdateSessionRequest = {
836
+ title: string;
837
+ };
838
+
477
839
  // --- Operator context controls (/clear, /compact) ----------------------------
478
840
 
479
841
  /** Outcome of a manual /compact trigger. */
@@ -1018,6 +1380,9 @@ export const KNOWN_USAGE_EVENT_TYPES = [
1018
1380
  "document.indexed",
1019
1381
  "scheduled_task.fired",
1020
1382
  "api_key.request",
1383
+ // sandbox warm-time metering (P2.1) — mirrors contracts UsageEventType.
1384
+ "sandbox.warm_seconds",
1385
+ "sandbox.warm_cost",
1021
1386
  ] as const;
1022
1387
 
1023
1388
  export type KnownUsageEventType = (typeof KNOWN_USAGE_EVENT_TYPES)[number];