@opengeni/sdk 0.27.0 → 0.28.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/sdk",
3
- "version": "0.27.0",
3
+ "version": "0.28.2",
4
4
  "description": "Framework-agnostic TypeScript SDK for the OpenGeni API: typed client, session lifecycle, SSE event streaming with reconnect + replay-by-sequence, and proxy re-streaming helpers.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -24,6 +24,8 @@ export {
24
24
  export type { ProxySessionEventStreamOptions, SseReStreamOptions } from "./proxy";
25
25
  export { parseSseStream } from "./sse";
26
26
  export type { SseMessage } from "./sse";
27
+ export { normalizeMcpOutput } from "./mcp-output";
28
+ export type { NormalizedMcpOutput } from "./mcp-output";
27
29
  // Desktop (noVNC) transport contract — pure, zero-dep (the RFB import lives in
28
30
  // @opengeni/react). URL assembler + connection state machine + rotation fence.
29
31
  export { desktopSocketUrl, nextDesktopState, applyUrlRotation } from "./desktop";
@@ -322,6 +324,7 @@ export type {
322
324
  SessionCommandReceipt,
323
325
  SteerSessionQueueItemRequest,
324
326
  WorkspaceInferenceControlResponse,
327
+ SessionPendingInputPreview,
325
328
  SessionSystemUpdate,
326
329
  SessionSystemUpdateKind,
327
330
  SessionSystemUpdateState,
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A transport-tolerant MCP tool result.
3
+ *
4
+ * `value` is the canonical machine-readable payload after recognized MCP/JSON
5
+ * envelopes are removed. `text` is the best presentation string without
6
+ * discarding structured data. `raw` always retains the original evidence.
7
+ */
8
+ export type NormalizedMcpOutput = Readonly<{
9
+ raw: unknown;
10
+ value: unknown;
11
+ text: string;
12
+ isError: boolean;
13
+ }>;
14
+
15
+ const MAX_MCP_OUTPUT_DEPTH = 8;
16
+ const RESULT_ENVELOPE_KEYS = new Set(["result", "isError", "jsonrpc", "id", "_meta"]);
17
+
18
+ /** Normalize common direct, JSON, and standard MCP result envelopes without throwing. */
19
+ export function normalizeMcpOutput(output: unknown): NormalizedMcpOutput {
20
+ const normalized = normalizeValue(output, 0, new Set<object>());
21
+ return {
22
+ raw: output,
23
+ value: normalized.value,
24
+ text: normalized.text,
25
+ isError: normalized.isError,
26
+ };
27
+ }
28
+
29
+ type NormalizedValue = Omit<NormalizedMcpOutput, "raw">;
30
+
31
+ function normalizeValue(value: unknown, depth: number, ancestors: Set<object>): NormalizedValue {
32
+ if (value === null || value === undefined) {
33
+ return { value, text: "", isError: false };
34
+ }
35
+ if (typeof value === "string") {
36
+ return normalizeText(value, depth, ancestors);
37
+ }
38
+ if (typeof value !== "object") {
39
+ return { value, text: String(value), isError: false };
40
+ }
41
+ if (depth >= MAX_MCP_OUTPUT_DEPTH || ancestors.has(value)) {
42
+ return { value, text: safeStringify(value), isError: false };
43
+ }
44
+
45
+ ancestors.add(value);
46
+ try {
47
+ if (Array.isArray(value)) {
48
+ return { value, text: safeStringify(value), isError: false };
49
+ }
50
+
51
+ const record = value as Record<string, unknown>;
52
+ const envelopeError = record.isError === true;
53
+
54
+ if (record.type === "text" && typeof record.text === "string") {
55
+ const normalized = normalizeText(record.text, depth + 1, ancestors);
56
+ return {
57
+ value: normalized.value,
58
+ text: record.text,
59
+ isError: envelopeError || normalized.isError,
60
+ };
61
+ }
62
+
63
+ if ("structuredContent" in record) {
64
+ const normalized = normalizeValue(record.structuredContent, depth + 1, ancestors);
65
+ return {
66
+ value: normalized.value,
67
+ text: firstMcpText(record.content) ?? normalized.text,
68
+ isError: envelopeError || normalized.isError,
69
+ };
70
+ }
71
+
72
+ if (isMcpContent(record.content)) {
73
+ const text = firstMcpText(record.content);
74
+ if (text !== null) {
75
+ const normalized = normalizeText(text, depth + 1, ancestors);
76
+ return {
77
+ value: normalized.value,
78
+ text,
79
+ isError: envelopeError || normalized.isError,
80
+ };
81
+ }
82
+ return {
83
+ value,
84
+ text: safeStringify(value),
85
+ isError: envelopeError,
86
+ };
87
+ }
88
+
89
+ if (isResultEnvelope(record)) {
90
+ const normalized = normalizeValue(record.result, depth + 1, ancestors);
91
+ return {
92
+ value: normalized.value,
93
+ text: normalized.text,
94
+ isError: envelopeError || normalized.isError,
95
+ };
96
+ }
97
+
98
+ return {
99
+ value,
100
+ text: safeStringify(value),
101
+ isError: envelopeError,
102
+ };
103
+ } finally {
104
+ ancestors.delete(value);
105
+ }
106
+ }
107
+
108
+ function normalizeText(text: string, depth: number, ancestors: Set<object>): NormalizedValue {
109
+ try {
110
+ const parsed: unknown = JSON.parse(text);
111
+ const normalized = normalizeValue(parsed, depth + 1, ancestors);
112
+ return {
113
+ value: normalized.value,
114
+ text,
115
+ isError: normalized.isError,
116
+ };
117
+ } catch {
118
+ return { value: text, text, isError: false };
119
+ }
120
+ }
121
+
122
+ function isMcpContent(value: unknown): value is readonly Record<string, unknown>[] {
123
+ return (
124
+ Array.isArray(value) &&
125
+ value.some(
126
+ (part) =>
127
+ part !== null &&
128
+ typeof part === "object" &&
129
+ typeof (part as { type?: unknown }).type === "string",
130
+ )
131
+ );
132
+ }
133
+
134
+ function firstMcpText(value: unknown): string | null {
135
+ if (!Array.isArray(value)) {
136
+ return null;
137
+ }
138
+ for (const part of value) {
139
+ if (
140
+ part !== null &&
141
+ typeof part === "object" &&
142
+ (part as { type?: unknown }).type === "text" &&
143
+ typeof (part as { text?: unknown }).text === "string"
144
+ ) {
145
+ return (part as { text: string }).text;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ function isResultEnvelope(record: Record<string, unknown>): boolean {
152
+ return "result" in record && Object.keys(record).every((key) => RESULT_ENVELOPE_KEYS.has(key));
153
+ }
154
+
155
+ function safeStringify(value: unknown): string {
156
+ try {
157
+ return JSON.stringify(value) ?? "";
158
+ } catch {
159
+ return String(value);
160
+ }
161
+ }
package/src/types.ts CHANGED
@@ -517,6 +517,7 @@ export type Session = {
517
517
  rigId: string | null;
518
518
  rigVersionId: string | null;
519
519
  firstPartyMcpPermissions: string[] | null;
520
+ firstPartyMcpTools: FirstPartyMcpToolName[] | null;
520
521
  mcpServers: SessionMcpServerMetadata[];
521
522
  parentSessionId: string | null;
522
523
  /** Immutable server-authored nested-agent lineage and policy snapshot. */
@@ -758,6 +759,9 @@ export const SESSION_EVENT_TYPES = [
758
759
  "goal.continuation",
759
760
  "system.update.pending",
760
761
  "system.update.delivered",
762
+ "system.update.superseded",
763
+ "system.update.cancelled",
764
+ "system.update.settled",
761
765
  "session.control.paused",
762
766
  "session.control.resumed",
763
767
  "session.control.steer_requested",
@@ -1594,6 +1598,7 @@ export type CreateSessionRequest = {
1594
1598
  expectedNewSessionDraftRevision?: number | undefined;
1595
1599
  maxNestedAgentDepth?: number | undefined;
1596
1600
  firstPartyMcpPermissions?: string[] | undefined;
1601
+ firstPartyMcpTools?: FirstPartyMcpToolName[] | undefined;
1597
1602
  mcpServers?: SessionMcpServerInput[] | undefined;
1598
1603
  // Shared-sandbox placement (mirror of `@opengeni/contracts` CreateSessionRequest.sandbox,
1599
1604
  // addendum 05 §D.1). Three-way union; OMITTED ⇒ the context-dependent server default
@@ -1659,6 +1664,58 @@ export type KnownPermission = (typeof KNOWN_PERMISSIONS)[number];
1659
1664
  */
1660
1665
  export type Permission = KnownPermission | (string & {});
1661
1666
 
1667
+ export type FirstPartyMcpToolName =
1668
+ | "set_session_title"
1669
+ | "goal_set"
1670
+ | "goal_update"
1671
+ | "goal_complete"
1672
+ | "goal_pause"
1673
+ | "memory_search"
1674
+ | "memory_save"
1675
+ | "memory_correct"
1676
+ | "sandboxes_list"
1677
+ | "sandbox_attach"
1678
+ | "sandbox_swap"
1679
+ | "run_on"
1680
+ | "sandbox_provision"
1681
+ | "rig_list"
1682
+ | "rig_get"
1683
+ | "rig_propose_change"
1684
+ | "rig_verify"
1685
+ | "rig_promote"
1686
+ | "sessions_list"
1687
+ | "session_get"
1688
+ | "session_events"
1689
+ | "session_create"
1690
+ | "session_send_message"
1691
+ | "session_pause"
1692
+ | "session_resume"
1693
+ | "session_steer"
1694
+ | "set_other_session_title"
1695
+ | "variable_set_list"
1696
+ | "environment_list"
1697
+ | "variable_set_set_variable"
1698
+ | "environment_set_variable"
1699
+ | "github_connect_link"
1700
+ | "github_token"
1701
+ | "github_repositories_list"
1702
+ | "social_connections_list"
1703
+ | "social_posts_recent"
1704
+ | "social_daily_analysis_context"
1705
+ | "scheduled_tasks_list"
1706
+ | "scheduled_tasks_get"
1707
+ | "scheduled_tasks_create"
1708
+ | "scheduled_tasks_update"
1709
+ | "scheduled_tasks_pause"
1710
+ | "scheduled_tasks_resume"
1711
+ | "scheduled_tasks_trigger"
1712
+ | "scheduled_tasks_delete"
1713
+ | "scheduled_task_runs_list"
1714
+ | "slack_bot_list_channels"
1715
+ | "slack_bot_channel_history"
1716
+ | "slack_bot_list_users"
1717
+ | "slack_bot_post_message";
1718
+
1662
1719
  export type ProductAccessMode = "local" | "configured" | "managed";
1663
1720
 
1664
1721
  export type ModelCapabilitySupportV1 = "supported" | "unsupported" | "unknown";
@@ -2371,8 +2428,20 @@ export type SessionQueueSnapshot = {
2371
2428
  /** The latest interrupted attempt has not yet durably proved physical quiescence. */
2372
2429
  stoppingPreviousAttempt: boolean;
2373
2430
  items: SessionTurn[];
2431
+ /** Canonical pending machine inputs. Events only invalidate this snapshot. */
2432
+ pendingInputs: SessionPendingInputPreview[];
2433
+ /** Exact next bounded input batch that will join an already-waiting prompt. */
2434
+ pendingInputAttachment: {
2435
+ turnId: string;
2436
+ inputIds: string[];
2437
+ } | null;
2374
2438
  };
2375
2439
 
2440
+ export type SessionPendingInputPreview = Pick<
2441
+ SessionSystemUpdate,
2442
+ "id" | "sessionId" | "kind" | "classification" | "sourceId" | "summary" | "createdAt"
2443
+ >;
2444
+
2376
2445
  export type SystemUpdateClassification = "success" | "failure" | "action_required" | "info";
2377
2446
 
2378
2447
  export type SessionSystemUpdateKind =
@@ -2384,7 +2453,6 @@ export type SessionSystemUpdateKind =
2384
2453
 
2385
2454
  export type SessionSystemUpdateState =
2386
2455
  | "pending"
2387
- | "deferred"
2388
2456
  | "delivered"
2389
2457
  | "cancelled"
2390
2458
  | "superseded"
@@ -2402,6 +2470,7 @@ export type SessionSystemUpdate = {
2402
2470
  lineage: Record<string, unknown>;
2403
2471
  state: SessionSystemUpdateState;
2404
2472
  deliveredTurnId: string | null;
2473
+ deliveredHistoryItemId: string | null;
2405
2474
  deliveredAt: string | null;
2406
2475
  createdAt: string;
2407
2476
  };