@opengeni/sdk 0.13.0 → 0.15.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
@@ -10,7 +10,6 @@ export type SessionStatus =
10
10
  | "requires_action"
11
11
  | "recovering"
12
12
  | "waiting_capacity"
13
- | "paused"
14
13
  | "failed"
15
14
  | "cancelled";
16
15
 
@@ -400,12 +399,7 @@ export type Session = {
400
399
  queueVersion: number;
401
400
  queueHeadPosition: number;
402
401
  queueTailPosition: number;
403
- controlState: "active" | "paused";
404
- controlGeneration: number;
405
- controlReason: string | null;
406
- controlChangedBy: string | null;
407
- controlChangedAt: string | null;
408
- workspaceRunExceptionGeneration: number | null;
402
+ effectiveControl: EffectiveSessionControl;
409
403
  lastSequence: number;
410
404
  /** Multi-account Codex (P1): the account this session is pinned to (null ⇒ follow workspace active). */
411
405
  codexPinnedCredentialId?: string | null;
@@ -467,7 +461,8 @@ export type SessionTurnStatus =
467
461
  | "completed"
468
462
  | "failed"
469
463
  | "cancelled"
470
- | "superseded";
464
+ | "superseded"
465
+ | "withdrawn_for_edit";
471
466
 
472
467
  export type SessionTurnSource =
473
468
  | "user"
@@ -556,6 +551,7 @@ export const SESSION_EVENT_TYPES = [
556
551
  "session.control.steer_requested",
557
552
  "workspace.inference.paused",
558
553
  "workspace.inference.resumed",
554
+ "session.queue.changed",
559
555
  "session.queue.prompt.cancelled",
560
556
  "session.queue.history",
561
557
  "turn.event.rejected_late",
@@ -1355,6 +1351,11 @@ export type ClientAuthConfig =
1355
1351
  | { mode: "configuredToken"; headerName: "authorization"; scheme: "bearer" }
1356
1352
  | { mode: "managedSession"; session: "cookie" };
1357
1353
 
1354
+ // Kept value-identical to @opengeni/contracts and pinned by the SDK contract
1355
+ // parity suite. The SDK has no runtime dependency on the Zod contracts package.
1356
+ export const OPENGENI_API_CONTRACT_REVISION = "2026-07-session-control-v1" as const;
1357
+ export const OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract" as const;
1358
+
1358
1359
  /**
1359
1360
  * Public, unauthenticated-by-default client bootstrap config returned by
1360
1361
  * `GET /v1/config/client`: which models + reasoning efforts are exposed, the
@@ -1364,6 +1365,8 @@ export type ClientAuthConfig =
1364
1365
  */
1365
1366
  export type ClientConfig = {
1366
1367
  deploymentRevision: string;
1368
+ apiContractRevision: typeof OPENGENI_API_CONTRACT_REVISION;
1369
+ serverVersion?: string | undefined;
1367
1370
  defaultModel: string;
1368
1371
  allowedModels: string[];
1369
1372
  models: ClientModel[];
@@ -1419,11 +1422,13 @@ export type Workspace = {
1419
1422
  externalId: string | null;
1420
1423
  agentInstructions: string | null;
1421
1424
  settings: Record<string, unknown>;
1422
- inferenceState?: "active" | "paused";
1423
- inferenceGeneration?: number;
1424
- inferenceReason?: string | null;
1425
- inferenceChangedBy?: string | null;
1426
- inferenceChangedAt?: string | null;
1425
+ inferenceControl: {
1426
+ state: "active" | "paused";
1427
+ revision: number;
1428
+ reason: string | null;
1429
+ changedBy: string | null;
1430
+ changedAt: string | null;
1431
+ };
1427
1432
  defaultRigId?: string | null;
1428
1433
  createdAt: string;
1429
1434
  updatedAt: string;
@@ -1561,29 +1566,83 @@ export type CompactSessionContextResult = {
1561
1566
 
1562
1567
  // --- Turn queue --------------------------------------------------------------
1563
1568
 
1569
+ export type EffectiveControlBlocker = {
1570
+ kind: "session" | "workspace";
1571
+ sessionId?: string | undefined;
1572
+ displayName: string;
1573
+ actor: string | null;
1574
+ reason: string | null;
1575
+ changedAt: string | null;
1576
+ revision: number;
1577
+ };
1578
+
1579
+ export type EffectiveControlResumeOption = {
1580
+ scope: "selected" | "session" | "workspace";
1581
+ targetId?: string | undefined;
1582
+ selectedStateAfter: "active" | "paused";
1583
+ remainingPrimaryBlocker?: EffectiveControlBlocker | undefined;
1584
+ impactCopy: string;
1585
+ };
1586
+
1587
+ export type EffectiveSessionControl = {
1588
+ state: "active" | "paused";
1589
+ controlVersion: number;
1590
+ controlEtag: string;
1591
+ directState: "active" | "paused";
1592
+ primaryBlocker: EffectiveControlBlocker | null;
1593
+ additionalBlockerCount: number;
1594
+ blockers: EffectiveControlBlocker[];
1595
+ resumeOptions: EffectiveControlResumeOption[];
1596
+ override: { rootSessionId: string; revision: number } | null;
1597
+ settlement: { state: "stopping"; attemptCount: number } | null;
1598
+ };
1599
+
1600
+ export type SessionCommandReceipt = {
1601
+ id: string;
1602
+ action: string;
1603
+ operationKey: string;
1604
+ targetSessionId: string | null;
1605
+ targetTurnId: string | null;
1606
+ appliedControlRevision: number | null;
1607
+ appliedQueueVersion: number | null;
1608
+ appliedTurnVersion: number | null;
1609
+ appliedDraftRevision: number | null;
1610
+ createdAt: string;
1611
+ };
1612
+
1613
+ export type ComposerDraft = {
1614
+ revision: number;
1615
+ text: string;
1616
+ resources: ResourceRef[];
1617
+ tools: ToolRef[];
1618
+ model: string;
1619
+ reasoningEffort: ReasoningEffort;
1620
+ sourceTurnId: string | null;
1621
+ sourceTurnVersion: number | null;
1622
+ updatedAt: string | null;
1623
+ };
1624
+
1564
1625
  export type SessionQueueSnapshot = {
1565
1626
  version: number;
1566
- controlState: "active" | "paused";
1567
- controlGeneration: number;
1568
- workspaceInferenceState: "active" | "paused";
1569
- workspaceInferenceGeneration: number;
1570
- workspaceRunExceptionGeneration: number | null;
1627
+ effectiveControl: EffectiveSessionControl;
1571
1628
  items: SessionTurn[];
1572
1629
  };
1573
1630
 
1574
1631
  export type SystemUpdateClassification = "success" | "failure" | "action_required" | "info";
1575
1632
 
1576
1633
  export type SessionSystemUpdateKind =
1577
- | "child_session_update"
1578
- | "scheduled_wake"
1579
- | "lifecycle_event"
1580
- | "runtime_notice";
1634
+ | "scheduled_occurrence"
1635
+ | "goal_continuation"
1636
+ | "agent_message"
1637
+ | "agent_steer_instruction"
1638
+ | "child_terminal_result";
1581
1639
 
1582
1640
  export type SessionSystemUpdateState =
1583
1641
  | "pending"
1584
1642
  | "deferred"
1585
1643
  | "delivered"
1586
1644
  | "cancelled"
1645
+ | "superseded"
1587
1646
  | "failed";
1588
1647
 
1589
1648
  export type SessionSystemUpdate = {
@@ -1603,33 +1662,72 @@ export type SessionSystemUpdate = {
1603
1662
  };
1604
1663
 
1605
1664
  export type SessionControlResponse = {
1606
- operationId: string;
1607
- event: SessionEvent;
1608
- controlState: "active" | "paused";
1609
- controlGeneration: number;
1610
- expectedActiveTurnId: string | null;
1611
- expectedExecutionGeneration: number | null;
1612
- expectedAttemptId: string | null;
1613
- deliveryEventId: string | null;
1614
- shouldSignalControl: boolean;
1615
- shouldWake: boolean;
1665
+ receipt: SessionCommandReceipt;
1666
+ effectiveControl: EffectiveSessionControl;
1667
+ interruptionCount: number;
1668
+ wakeCount: number;
1616
1669
  };
1617
1670
 
1618
1671
  export type WorkspaceInferenceControlResponse = {
1619
- operationId: string;
1672
+ receipt: SessionCommandReceipt;
1620
1673
  state: "active" | "paused";
1621
- generation: number;
1622
- affectedSessionIds: string[];
1623
- controlSessionIds: string[];
1624
- exceptionSessionIds: string[];
1674
+ revision: number;
1675
+ interruptionCount: number;
1676
+ wakeCount: number;
1677
+ };
1678
+
1679
+ export type WorkspaceControlEvent = {
1680
+ id: string;
1681
+ workspaceId: string;
1682
+ /** Same monotonic value as revision; named sequence for SSE resume cursors. */
1683
+ sequence: number;
1684
+ revision: number;
1685
+ type: "workspace.control.changed";
1686
+ scope: "workspace" | "session";
1687
+ rootSessionId: string | null;
1688
+ action: "pause" | "resume";
1689
+ automatic: boolean;
1690
+ reason: string | null;
1691
+ actor: string;
1692
+ occurredAt: string;
1625
1693
  };
1626
1694
 
1627
1695
  export type SessionQueueMutationResponse = {
1696
+ receipt: SessionCommandReceipt;
1628
1697
  snapshot: SessionQueueSnapshot;
1629
- events: SessionEvent[];
1630
- shouldWake: boolean;
1698
+ draft?: ComposerDraft;
1631
1699
  };
1632
1700
 
1701
+ export type MoveSessionQueueItemRequest = {
1702
+ clientEventId: string;
1703
+ expectedQueueVersion: number;
1704
+ beforeTurnId: string | null;
1705
+ };
1706
+
1707
+ export type EditSessionQueueItemRequest = {
1708
+ clientEventId: string;
1709
+ expectedTurnVersion: number;
1710
+ expectedDraftRevision: number;
1711
+ replaceDraft: boolean;
1712
+ };
1713
+
1714
+ export type SteerSessionQueueItemRequest = {
1715
+ clientEventId: string;
1716
+ expectedTurnVersion: number;
1717
+ controlEtag?: string;
1718
+ };
1719
+
1720
+ export type DeleteSessionQueueItemRequest = {
1721
+ clientEventId: string;
1722
+ expectedTurnVersion: number;
1723
+ reason?: string;
1724
+ };
1725
+
1726
+ export type SaveComposerDraftRequest = Omit<
1727
+ ComposerDraft,
1728
+ "revision" | "sourceTurnId" | "sourceTurnVersion" | "updatedAt"
1729
+ > & { expectedRevision: number };
1730
+
1633
1731
  // --- Scheduled tasks: requests + runs ----------------------------------------
1634
1732
 
1635
1733
  /** Input shape for agent config on create/update (server applies defaults). */
@@ -0,0 +1,101 @@
1
+ import { isAbortError, isRetryableStreamError, OpenGeniStreamError } from "./errors";
2
+ import { parseSseStream } from "./sse";
3
+ import type { StreamSessionEventsOptions } from "./stream";
4
+ import type { WorkspaceControlEvent } from "./types";
5
+
6
+ export type WorkspaceControlStreamTransport = {
7
+ /** The server replays every durable event after the cursor before going live. */
8
+ openStream: (
9
+ after: number,
10
+ signal: AbortSignal | undefined,
11
+ ) => Promise<ReadableStream<Uint8Array>>;
12
+ };
13
+
14
+ /**
15
+ * Reconnecting workspace invalidation stream. Control revisions are monotonic
16
+ * but can begin above one after the one-way migration, so unlike conversation
17
+ * events this stream intentionally permits sparse sequence values.
18
+ */
19
+ export async function* streamWorkspaceControlEvents(
20
+ transport: WorkspaceControlStreamTransport,
21
+ options: StreamSessionEventsOptions = {},
22
+ ): AsyncGenerator<WorkspaceControlEvent, void, void> {
23
+ const signal = options.signal;
24
+ const reconnect = options.reconnect ?? true;
25
+ const baseDelayMs = options.reconnectDelayMs ?? 500;
26
+ const maxDelayMs = options.maxReconnectDelayMs ?? 10_000;
27
+ const maxAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
28
+ let cursor = options.after ?? 0;
29
+ let failures = 0;
30
+ let delayMs = baseDelayMs;
31
+ let everConnected = false;
32
+
33
+ for (;;) {
34
+ if (signal?.aborted) return;
35
+ options.onStateChange?.(everConnected || failures > 0 ? "reconnecting" : "connecting");
36
+ const cursorAtOpen = cursor;
37
+ try {
38
+ const body = await transport.openStream(cursor, signal);
39
+ everConnected = true;
40
+ failures = 0;
41
+ delayMs = baseDelayMs;
42
+ await options.beforeLive?.();
43
+ options.onStateChange?.("live");
44
+ for await (const message of parseSseStream(body)) {
45
+ if (signal?.aborted) return;
46
+ const event = parseWorkspaceControlEvent(message.data);
47
+ if (!event || event.sequence <= cursor) continue;
48
+ cursor = event.sequence;
49
+ yield event;
50
+ }
51
+ if (!reconnect) return;
52
+ if (cursor === cursorAtOpen) await sleep(baseDelayMs, signal);
53
+ continue;
54
+ } catch (error) {
55
+ if (signal?.aborted || isAbortError(error)) return;
56
+ if (!reconnect || !isRetryableStreamError(error)) throw error;
57
+ failures += 1;
58
+ if (failures > maxAttempts) {
59
+ throw new OpenGeniStreamError(
60
+ `workspace control stream gave up after ${maxAttempts} reconnect attempts: ${
61
+ error instanceof Error ? error.message : String(error)
62
+ }`,
63
+ );
64
+ }
65
+ }
66
+ await sleep(delayMs, signal);
67
+ delayMs = Math.min(Math.max(delayMs * 2, baseDelayMs), maxDelayMs);
68
+ }
69
+ }
70
+
71
+ function parseWorkspaceControlEvent(data: string): WorkspaceControlEvent | null {
72
+ let value: unknown;
73
+ try {
74
+ value = JSON.parse(data);
75
+ } catch {
76
+ return null;
77
+ }
78
+ if (
79
+ typeof value !== "object" ||
80
+ value === null ||
81
+ (value as { type?: unknown }).type !== "workspace.control.changed" ||
82
+ typeof (value as { id?: unknown }).id !== "string" ||
83
+ typeof (value as { sequence?: unknown }).sequence !== "number"
84
+ ) {
85
+ return null;
86
+ }
87
+ return value as WorkspaceControlEvent;
88
+ }
89
+
90
+ async function sleep(delayMs: number, signal: AbortSignal | undefined): Promise<void> {
91
+ if (signal?.aborted || delayMs <= 0) return;
92
+ await new Promise<void>((resolve) => {
93
+ const timer = setTimeout(done, delayMs);
94
+ function done() {
95
+ clearTimeout(timer);
96
+ signal?.removeEventListener("abort", done);
97
+ resolve();
98
+ }
99
+ signal?.addEventListener("abort", done, { once: true });
100
+ });
101
+ }