@opengeni/sdk 0.48.0 → 0.57.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.
Files changed (122) hide show
  1. package/README.md +147 -1
  2. package/dist/artifact-client.d.ts +20 -2
  3. package/dist/artifacts.d.ts +4 -0
  4. package/dist/artifacts.js +11 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/{chunk-V5F253OG.js → chunk-GU6JF75T.js} +6 -3
  7. package/dist/chunk-GU6JF75T.js.map +1 -0
  8. package/dist/chunk-HJ3HHUT5.js +222 -0
  9. package/dist/chunk-HJ3HHUT5.js.map +1 -0
  10. package/dist/{chunk-KIHGPM7H.js → chunk-JDLDDRNE.js} +20 -14
  11. package/dist/chunk-JDLDDRNE.js.map +1 -0
  12. package/dist/chunk-JRPFWI6T.js +128 -0
  13. package/dist/chunk-JRPFWI6T.js.map +1 -0
  14. package/dist/{chunk-MBGBLVUV.js → chunk-PKQ377ED.js} +1725 -555
  15. package/dist/chunk-PKQ377ED.js.map +1 -0
  16. package/dist/chunk-ST5DDKJP.js +987 -0
  17. package/dist/chunk-ST5DDKJP.js.map +1 -0
  18. package/dist/chunk-VWE2QIVO.js +1162 -0
  19. package/dist/chunk-VWE2QIVO.js.map +1 -0
  20. package/dist/chunk-VYCTL62C.js +230 -0
  21. package/dist/chunk-VYCTL62C.js.map +1 -0
  22. package/dist/{chunk-DXDL7EEW.js → chunk-YKZME56C.js} +197 -113
  23. package/dist/chunk-YKZME56C.js.map +1 -0
  24. package/dist/client.d.ts +240 -16
  25. package/dist/codex-realtime-controller.d.ts +9 -6
  26. package/dist/codex-realtime-controller.js +2 -2
  27. package/dist/codex-realtime-lifecycle.d.ts +1 -1
  28. package/dist/codex-realtime-v3.d.ts +3 -3
  29. package/dist/codex-realtime.d.ts +1 -1
  30. package/dist/company-profile.d.ts +100 -0
  31. package/dist/core.d.ts +5 -5
  32. package/dist/core.js +10 -5
  33. package/dist/desktop.d.ts +9 -1
  34. package/dist/editable-artifact-resources.d.ts +96 -0
  35. package/dist/editable-artifacts/browser-session.d.ts +35 -0
  36. package/dist/editable-artifacts/controller.d.ts +60 -0
  37. package/dist/editable-artifacts/errors.d.ts +23 -0
  38. package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
  39. package/dist/editable-artifacts/index.d.ts +19 -0
  40. package/dist/editable-artifacts/pool.d.ts +15 -0
  41. package/dist/editable-artifacts/session.d.ts +96 -0
  42. package/dist/editable-artifacts/storage.d.ts +72 -0
  43. package/dist/editable-artifacts/types.d.ts +416 -0
  44. package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
  45. package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
  46. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
  47. package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
  48. package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
  49. package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
  50. package/dist/editable-artifacts-worker.d.ts +8 -0
  51. package/dist/editable-artifacts-worker.js +1708 -0
  52. package/dist/editable-artifacts-worker.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +6 -0
  54. package/dist/editable-artifacts.js +5954 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/gateway-realtime-transport.d.ts +2 -1
  57. package/dist/gateway-realtime-transport.js +5 -3
  58. package/dist/index.d.ts +45 -35
  59. package/dist/index.js +134 -64
  60. package/dist/index.js.map +1 -1
  61. package/dist/interaction-revision-stream.d.ts +11 -0
  62. package/dist/interaction.d.ts +1484 -0
  63. package/dist/interaction.js +81 -0
  64. package/dist/interaction.js.map +1 -0
  65. package/dist/memory-slack-client.d.ts +13 -0
  66. package/dist/memory-slack-delivery.d.ts +99 -0
  67. package/dist/memory-slack.d.ts +4 -0
  68. package/dist/memory-slack.js +44 -0
  69. package/dist/memory-slack.js.map +1 -0
  70. package/dist/proxy.d.ts +3 -3
  71. package/dist/realtime.d.ts +14 -14
  72. package/dist/realtime.js +11 -7
  73. package/dist/realtime.js.map +1 -1
  74. package/dist/retained-artifacts.d.ts +12 -0
  75. package/dist/stream.d.ts +1 -1
  76. package/dist/terminal.d.ts +1 -1
  77. package/dist/types.d.ts +1347 -34
  78. package/dist/workspace-control-stream.d.ts +2 -2
  79. package/dist/workspace-live-stream.d.ts +14 -0
  80. package/dist/workspace-state.d.ts +1 -1
  81. package/package.json +29 -3
  82. package/src/artifact-client.ts +129 -0
  83. package/src/artifacts.ts +17 -0
  84. package/src/client.ts +3022 -910
  85. package/src/codex-realtime-controller.ts +25 -7
  86. package/src/codex-realtime-lifecycle.ts +1 -0
  87. package/src/company-profile.ts +92 -0
  88. package/src/desktop.ts +11 -1
  89. package/src/editable-artifact-resources.ts +119 -0
  90. package/src/editable-artifacts/browser-session.ts +176 -0
  91. package/src/editable-artifacts/controller.ts +2632 -0
  92. package/src/editable-artifacts/errors.ts +52 -0
  93. package/src/editable-artifacts/http-live-transport.ts +1449 -0
  94. package/src/editable-artifacts/index.ts +175 -0
  95. package/src/editable-artifacts/pool.ts +81 -0
  96. package/src/editable-artifacts/session.ts +778 -0
  97. package/src/editable-artifacts/storage.ts +1718 -0
  98. package/src/editable-artifacts/types.ts +499 -0
  99. package/src/editable-artifacts/worker/browser-client.ts +1167 -0
  100. package/src/editable-artifacts/worker/browser-entry.ts +57 -0
  101. package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
  102. package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
  103. package/src/editable-artifacts/worker/runtime.ts +1204 -0
  104. package/src/editable-artifacts/worker/wire-codec.ts +852 -0
  105. package/src/editable-artifacts-worker.ts +8 -0
  106. package/src/editable-artifacts.ts +6 -0
  107. package/src/errors.ts +16 -2
  108. package/src/gateway-realtime-transport.ts +252 -109
  109. package/src/index.ts +215 -1
  110. package/src/interaction-revision-stream.ts +117 -0
  111. package/src/interaction.ts +2874 -0
  112. package/src/memory-slack-client.ts +71 -0
  113. package/src/memory-slack-delivery.ts +128 -0
  114. package/src/memory-slack.ts +19 -0
  115. package/src/realtime.ts +14 -4
  116. package/src/retained-artifacts.ts +326 -0
  117. package/src/types.ts +1636 -52
  118. package/src/workspace-live-stream.ts +137 -0
  119. package/dist/chunk-DXDL7EEW.js.map +0 -1
  120. package/dist/chunk-KIHGPM7H.js.map +0 -1
  121. package/dist/chunk-MBGBLVUV.js.map +0 -1
  122. package/dist/chunk-V5F253OG.js.map +0 -1
@@ -0,0 +1,71 @@
1
+ import type { OpenGeniClient as OpenGeniCoreClient } from "./client";
2
+ import type {
3
+ MemorySlackPublication,
4
+ MemorySlackPublicationActionRequest,
5
+ MemorySlackPublicationConfiguration,
6
+ MemorySlackPublicationConfigurationResponse,
7
+ MemorySlackPublicationHistoryResponse,
8
+ SlackPublicationChannelListResponse,
9
+ UpdateMemorySlackPublicationConfigurationRequest,
10
+ } from "./memory-slack-delivery";
11
+
12
+ export type OpenGeniMemorySlackTransport = Pick<OpenGeniCoreClient, "requestJson">;
13
+
14
+ /** Optional Memory-to-Slack API surface, isolated from the eager core client graph. */
15
+ export class OpenGeniMemorySlackClient {
16
+ constructor(private readonly client: OpenGeniMemorySlackTransport) {}
17
+
18
+ async getMemorySlackPublicationConfiguration(
19
+ workspaceId: string,
20
+ ): Promise<MemorySlackPublicationConfigurationResponse> {
21
+ return await this.client.requestJson<MemorySlackPublicationConfigurationResponse>(
22
+ "GET",
23
+ `/v1/workspaces/${workspaceId}/memory-slack-publications/configuration`,
24
+ );
25
+ }
26
+
27
+ async updateMemorySlackPublicationConfiguration(
28
+ workspaceId: string,
29
+ request: UpdateMemorySlackPublicationConfigurationRequest,
30
+ ): Promise<MemorySlackPublicationConfiguration> {
31
+ return await this.client.requestJson<MemorySlackPublicationConfiguration>(
32
+ "PUT",
33
+ `/v1/workspaces/${workspaceId}/memory-slack-publications/configuration`,
34
+ request,
35
+ );
36
+ }
37
+
38
+ async listMemorySlackPublicationChannels(
39
+ workspaceId: string,
40
+ connectionId: string,
41
+ cursor?: string,
42
+ ): Promise<SlackPublicationChannelListResponse> {
43
+ const query = new URLSearchParams({ connectionId });
44
+ if (cursor) query.set("cursor", cursor);
45
+ return await this.client.requestJson<SlackPublicationChannelListResponse>(
46
+ "GET",
47
+ `/v1/workspaces/${workspaceId}/memory-slack-publications/channels?${query}`,
48
+ );
49
+ }
50
+
51
+ async listMemorySlackPublications(
52
+ workspaceId: string,
53
+ ): Promise<MemorySlackPublicationHistoryResponse> {
54
+ return await this.client.requestJson<MemorySlackPublicationHistoryResponse>(
55
+ "GET",
56
+ `/v1/workspaces/${workspaceId}/memory-slack-publications`,
57
+ );
58
+ }
59
+
60
+ async actOnMemorySlackPublication(
61
+ workspaceId: string,
62
+ publicationId: string,
63
+ request: MemorySlackPublicationActionRequest,
64
+ ): Promise<MemorySlackPublication> {
65
+ return await this.client.requestJson<MemorySlackPublication>(
66
+ "POST",
67
+ `/v1/workspaces/${workspaceId}/memory-slack-publications/${encodeURIComponent(publicationId)}/action`,
68
+ request,
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,128 @@
1
+ export type MemorySlackImportance = "major" | "normal" | "minor";
2
+ export type MemorySlackRequestedMode = "auto" | "review" | "never";
3
+
4
+ export type MemorySlackPublicationDistribution = {
5
+ importance: MemorySlackImportance;
6
+ audience: "workspace";
7
+ slackMode: MemorySlackRequestedMode;
8
+ shareSummary: string;
9
+ };
10
+
11
+ export type MemorySlackPublicationConfiguration = {
12
+ id: string;
13
+ workspaceId: string;
14
+ revision: number;
15
+ enabled: boolean;
16
+ connectionId: string | null;
17
+ slackTeamId: string | null;
18
+ slackChannelId: string | null;
19
+ slackChannelName: string | null;
20
+ autoImportances: MemorySlackImportance[];
21
+ reviewImportances: MemorySlackImportance[];
22
+ createdBySubjectId: string;
23
+ createdAt: string;
24
+ };
25
+
26
+ export type UpdateMemorySlackPublicationConfigurationRequest = {
27
+ expectedRevision: number;
28
+ enabled: boolean;
29
+ connectionId: string | null;
30
+ slackChannelId: string | null;
31
+ slackChannelName: string | null;
32
+ autoImportances: MemorySlackImportance[];
33
+ reviewImportances: MemorySlackImportance[];
34
+ };
35
+
36
+ export type MemorySlackPublicationConfigurationResponse = {
37
+ current: MemorySlackPublicationConfiguration | null;
38
+ history: MemorySlackPublicationConfiguration[];
39
+ };
40
+
41
+ export type MemorySlackPublicationState =
42
+ | "review_pending"
43
+ | "queued"
44
+ | "delivering"
45
+ | "retry_wait"
46
+ | "delivered"
47
+ | "rejected"
48
+ | "failed"
49
+ | "cancelled";
50
+
51
+ export type MemorySlackPublicationReceiptKind =
52
+ | "enqueued"
53
+ | "review_approved"
54
+ | "review_rejected"
55
+ | "delivery_claimed"
56
+ | "retry_scheduled"
57
+ | "delivered"
58
+ | "failed"
59
+ | "cancelled"
60
+ | "manual_retry";
61
+
62
+ export type MemorySlackPublicationReceipt = {
63
+ id: string;
64
+ publicationId: string;
65
+ sequence: number;
66
+ kind: MemorySlackPublicationReceiptKind;
67
+ state: MemorySlackPublicationState;
68
+ attemptNumber: number;
69
+ actorKind: "human" | "agent" | "service";
70
+ actorSubjectId: string;
71
+ operationId: string;
72
+ errorCode: string | null;
73
+ retryAt: string | null;
74
+ slackChannelId: string | null;
75
+ slackMessageTimestamp: string | null;
76
+ createdAt: string;
77
+ };
78
+
79
+ export type MemorySlackPublication = {
80
+ id: string;
81
+ workspaceId: string;
82
+ configurationRevision: number;
83
+ connectionId: string;
84
+ slackTeamId: string;
85
+ slackChannelId: string;
86
+ sourceType: "workspace_memory" | "durable_learning";
87
+ sourceId: string;
88
+ sourceVersion: string | null;
89
+ importance: MemorySlackImportance;
90
+ deliveryMode: "auto" | "review";
91
+ state: MemorySlackPublicationState;
92
+ summary: string;
93
+ sourceLabel: string;
94
+ authoritativePath: string | null;
95
+ initiatorKind: "human" | "agent" | "service";
96
+ initiatorSubjectId: string;
97
+ initiatingHumanSubjectId: string | null;
98
+ attemptCount: number;
99
+ retryAt: string | null;
100
+ lastErrorCode: string | null;
101
+ slackMessageTimestamp: string | null;
102
+ deliveredAt: string | null;
103
+ terminalAt: string | null;
104
+ createdAt: string;
105
+ updatedAt: string;
106
+ receipts: MemorySlackPublicationReceipt[];
107
+ };
108
+
109
+ export type MemorySlackPublicationHistoryResponse = {
110
+ publications: MemorySlackPublication[];
111
+ nextCursor: string | null;
112
+ };
113
+
114
+ export type MemorySlackPublicationActionRequest = {
115
+ action: "approve" | "reject" | "retry";
116
+ expectedState: MemorySlackPublicationState;
117
+ };
118
+
119
+ export type SlackPublicationChannel = {
120
+ id: string;
121
+ name: string | null;
122
+ isPrivate: boolean;
123
+ };
124
+
125
+ export type SlackPublicationChannelListResponse = {
126
+ channels: SlackPublicationChannel[];
127
+ nextCursor: string | null;
128
+ };
@@ -0,0 +1,19 @@
1
+ /** Optional Memory-to-Slack API client; import this entry only where the capability is used. */
2
+ export { OpenGeniMemorySlackClient } from "./memory-slack-client";
3
+ export type { OpenGeniMemorySlackTransport } from "./memory-slack-client";
4
+ export type {
5
+ MemorySlackImportance,
6
+ MemorySlackPublication,
7
+ MemorySlackPublicationActionRequest,
8
+ MemorySlackPublicationConfiguration,
9
+ MemorySlackPublicationConfigurationResponse,
10
+ MemorySlackPublicationDistribution,
11
+ MemorySlackPublicationHistoryResponse,
12
+ MemorySlackPublicationReceipt,
13
+ MemorySlackPublicationReceiptKind,
14
+ MemorySlackPublicationState,
15
+ MemorySlackRequestedMode,
16
+ SlackPublicationChannel,
17
+ SlackPublicationChannelListResponse,
18
+ UpdateMemorySlackPublicationConfigurationRequest,
19
+ } from "./memory-slack-delivery";
package/src/realtime.ts CHANGED
@@ -20,7 +20,10 @@ import {
20
20
  type CreateCodexRealtimeControllerOptions,
21
21
  type RealtimeControllerTransportStarter,
22
22
  } from "./codex-realtime-controller";
23
- import { createGatewayRealtimeTransportStarter } from "./gateway-realtime-transport";
23
+ import {
24
+ createGatewayRealtimeTransportStarter,
25
+ createXaiSubscriptionRealtimeTransportStarter,
26
+ } from "./gateway-realtime-transport";
24
27
  import type { SessionRealtimeModel, WorkspaceRealtimeModelCatalogResponse } from "./types";
25
28
 
26
29
  /** Exact backend-facing methods required by the batteries-included realtime SDK. */
@@ -30,7 +33,7 @@ export type SessionRealtimeClientLike = CodexRealtimeControllerClient & {
30
33
  ): Promise<WorkspaceRealtimeModelCatalogResponse>;
31
34
  };
32
35
 
33
- export type SessionRealtimeTransportKind = "codex" | "gateway";
36
+ export type SessionRealtimeTransportKind = "codex" | "gateway" | "xai-subscription";
34
37
 
35
38
  /** Provider-neutral names for the existing, battle-tested controller projection. */
36
39
  export type SessionRealtimeController = CodexRealtimeController;
@@ -53,7 +56,9 @@ export type CreateSessionRealtimeControllerOptions = Omit<
53
56
  export function sessionRealtimeTransportKind(
54
57
  model: SessionRealtimeModel,
55
58
  ): SessionRealtimeTransportKind {
56
- return model === "gpt-live-1-boulder-alpha" ? "codex" : "gateway";
59
+ if (model === "gpt-live-1-boulder-alpha") return "codex";
60
+ if (model === "supergrok/grok-voice-think-fast-2.0") return "xai-subscription";
61
+ return "gateway";
57
62
  }
58
63
 
59
64
  /**
@@ -68,13 +73,18 @@ export function createSessionRealtimeController(
68
73
  return createCodexRealtimeController({
69
74
  ...options,
70
75
  ownerStorageNamespace: sessionRealtimeOwnerStorageNamespace(options.model),
71
- ...(transport === "gateway" ? { startTransport: createGatewayRealtimeTransportStarter() } : {}),
76
+ ...(transport === "gateway"
77
+ ? { startTransport: createGatewayRealtimeTransportStarter() }
78
+ : transport === "xai-subscription"
79
+ ? { startTransport: createXaiSubscriptionRealtimeTransportStarter() }
80
+ : {}),
72
81
  });
73
82
  }
74
83
 
75
84
  export {
76
85
  createCodexRealtimeController,
77
86
  createGatewayRealtimeTransportStarter,
87
+ createXaiSubscriptionRealtimeTransportStarter,
78
88
  hasStoredSessionRealtimeOwnerProof,
79
89
  sessionRealtimeOwnerStorageKey,
80
90
  sessionRealtimeOwnerStorageNamespace,
@@ -0,0 +1,326 @@
1
+ import type {
2
+ GeneratedImageReceipt,
3
+ GeneratedVideoReceipt,
4
+ MediaGenerationResult,
5
+ RetainedArtifactReference,
6
+ } from "./types";
7
+
8
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
9
+ const SHA256 = /^[0-9a-f]{64}$/;
10
+ const ISO_DATETIME_WITH_OFFSET =
11
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
12
+ const WORKSPACE_PATH = /^\/v1\/workspaces\/([0-9a-f-]+)\/artifacts\/([0-9a-f-]+)\/content$/;
13
+ const MAX_BYTES = 64 * 1024 * 1024;
14
+ const MAX_VIDEO_BYTES = 512 * 1024 * 1024;
15
+ const MAX_DIMENSION = 16_384;
16
+ const MAX_PIXELS = 67_108_864;
17
+ const MAX_RANGE_BYTES = 1024 * 1024;
18
+
19
+ /** Zero-dependency validation for the SDK's permanent generated-image wire receipt. */
20
+ export function parseRetainedGeneratedImageReference(
21
+ value: unknown,
22
+ expectedWorkspaceId?: string,
23
+ ): RetainedArtifactReference | null {
24
+ if (!recordWithKeys(value, GENERATED_IMAGE_REFERENCE_KEYS)) return null;
25
+ const artifact = value as Record<string, unknown>;
26
+ if (
27
+ artifact.available !== true ||
28
+ artifact.kind !== "generated_image" ||
29
+ typeof artifact.artifactId !== "string" ||
30
+ !UUID.test(artifact.artifactId) ||
31
+ !["image/png", "image/jpeg", "image/webp"].includes(String(artifact.contentType)) ||
32
+ !safeIntegerBetween(artifact.originalBytes, 1, MAX_BYTES) ||
33
+ typeof artifact.sha256 !== "string" ||
34
+ !SHA256.test(artifact.sha256) ||
35
+ typeof artifact.retainedAt !== "string" ||
36
+ artifact.retainedAt.length > 64 ||
37
+ !ISO_DATETIME_WITH_OFFSET.test(artifact.retainedAt) ||
38
+ !Number.isFinite(Date.parse(artifact.retainedAt)) ||
39
+ !recordWithKeys(artifact.dimensions, DIMENSION_KEYS) ||
40
+ !recordWithKeys(artifact.retention, RETENTION_KEYS) ||
41
+ !recordWithKeys(artifact.retrieval, RETRIEVAL_KEYS)
42
+ ) {
43
+ return null;
44
+ }
45
+ const dimensions = artifact.dimensions as Record<string, unknown>;
46
+ if (
47
+ !safeIntegerBetween(dimensions.width, 1, MAX_DIMENSION) ||
48
+ !safeIntegerBetween(dimensions.height, 1, MAX_DIMENSION) ||
49
+ Number(dimensions.width) * Number(dimensions.height) > MAX_PIXELS
50
+ ) {
51
+ return null;
52
+ }
53
+ const retention = artifact.retention as Record<string, unknown>;
54
+ if (retention.policy !== "workspace_file" || retention.expiresAt !== null) return null;
55
+ const retrieval = artifact.retrieval as Record<string, unknown>;
56
+ if (
57
+ retrieval.method !== "GET" ||
58
+ retrieval.acceptRanges !== "bytes" ||
59
+ retrieval.maxRangeBytes !== MAX_RANGE_BYTES ||
60
+ typeof retrieval.path !== "string" ||
61
+ retrieval.path.length > 256
62
+ ) {
63
+ return null;
64
+ }
65
+ const match = WORKSPACE_PATH.exec(retrieval.path);
66
+ if (
67
+ !match ||
68
+ !UUID.test(match[1] ?? "") ||
69
+ match[2] !== artifact.artifactId ||
70
+ (expectedWorkspaceId !== undefined && match[1] !== expectedWorkspaceId)
71
+ ) {
72
+ return null;
73
+ }
74
+ return value as RetainedArtifactReference;
75
+ }
76
+
77
+ export function generatedImageSandboxPathMatches(
78
+ artifact: RetainedArtifactReference,
79
+ sandboxPath: unknown,
80
+ ): sandboxPath is string {
81
+ if (typeof sandboxPath !== "string") return false;
82
+ const extension =
83
+ artifact.contentType === "image/png"
84
+ ? "png"
85
+ : artifact.contentType === "image/jpeg"
86
+ ? "jpg"
87
+ : artifact.contentType === "image/webp"
88
+ ? "webp"
89
+ : null;
90
+ return (
91
+ extension !== null &&
92
+ sandboxPath ===
93
+ `/workspace/generated-images/generated-image-${artifact.artifactId}.${extension}`
94
+ );
95
+ }
96
+
97
+ /** Parse the complete closed generated-image tool result. */
98
+ export function parseGeneratedImageReceipt(
99
+ value: unknown,
100
+ expectedWorkspaceId?: string,
101
+ ): GeneratedImageReceipt | null {
102
+ if (typeof value === "string" && value.length <= 4_096) {
103
+ try {
104
+ return parseGeneratedImageReceipt(JSON.parse(value), expectedWorkspaceId);
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ if (!recordWithKeys(value, GENERATED_IMAGE_RECEIPT_KEYS)) return null;
110
+ if (value.type !== "generated_image") return null;
111
+ const artifact = parseRetainedGeneratedImageReference(value.artifact, expectedWorkspaceId);
112
+ if (!artifact || !generatedImageSandboxPathMatches(artifact, value.sandboxPath)) return null;
113
+ return { type: "generated_image", artifact, sandboxPath: value.sandboxPath };
114
+ }
115
+
116
+ /** Zero-dependency validation for a permanent generated-video reference. */
117
+ export function parseRetainedGeneratedVideoReference(
118
+ value: unknown,
119
+ expectedWorkspaceId?: string,
120
+ ): RetainedArtifactReference | null {
121
+ if (!recordWithKeys(value, GENERATED_IMAGE_REFERENCE_KEYS)) return null;
122
+ const artifact = value as Record<string, unknown>;
123
+ if (
124
+ artifact.available !== true ||
125
+ artifact.kind !== "generated_video" ||
126
+ typeof artifact.artifactId !== "string" ||
127
+ !UUID.test(artifact.artifactId) ||
128
+ artifact.contentType !== "video/mp4" ||
129
+ !safeIntegerBetween(artifact.originalBytes, 1, MAX_VIDEO_BYTES) ||
130
+ typeof artifact.sha256 !== "string" ||
131
+ !SHA256.test(artifact.sha256) ||
132
+ typeof artifact.retainedAt !== "string" ||
133
+ artifact.retainedAt.length > 64 ||
134
+ !ISO_DATETIME_WITH_OFFSET.test(artifact.retainedAt) ||
135
+ !Number.isFinite(Date.parse(artifact.retainedAt)) ||
136
+ !recordWithKeys(artifact.dimensions, DIMENSION_KEYS) ||
137
+ !recordWithKeys(artifact.retention, RETENTION_KEYS) ||
138
+ !recordWithKeys(artifact.retrieval, RETRIEVAL_KEYS)
139
+ ) {
140
+ return null;
141
+ }
142
+ const dimensions = artifact.dimensions as Record<string, unknown>;
143
+ if (
144
+ !safeIntegerBetween(dimensions.width, 1, 8_192) ||
145
+ !safeIntegerBetween(dimensions.height, 1, 8_192)
146
+ ) {
147
+ return null;
148
+ }
149
+ const retention = artifact.retention as Record<string, unknown>;
150
+ if (retention.policy !== "workspace_file" || retention.expiresAt !== null) return null;
151
+ const retrieval = artifact.retrieval as Record<string, unknown>;
152
+ if (
153
+ retrieval.method !== "GET" ||
154
+ retrieval.acceptRanges !== "bytes" ||
155
+ retrieval.maxRangeBytes !== MAX_RANGE_BYTES ||
156
+ typeof retrieval.path !== "string" ||
157
+ retrieval.path.length > 256
158
+ ) {
159
+ return null;
160
+ }
161
+ const match = WORKSPACE_PATH.exec(retrieval.path);
162
+ if (
163
+ !match ||
164
+ !UUID.test(match[1] ?? "") ||
165
+ match[2] !== artifact.artifactId ||
166
+ (expectedWorkspaceId !== undefined && match[1] !== expectedWorkspaceId)
167
+ ) {
168
+ return null;
169
+ }
170
+ return value as RetainedArtifactReference;
171
+ }
172
+
173
+ /** Parse the compact successful video result placed in model/timeline history. */
174
+ export function parseGeneratedVideoReceipt(
175
+ value: unknown,
176
+ expectedWorkspaceId?: string,
177
+ ): GeneratedVideoReceipt | null {
178
+ if (typeof value === "string" && value.length <= 8_192) {
179
+ try {
180
+ return parseGeneratedVideoReceipt(JSON.parse(value), expectedWorkspaceId);
181
+ } catch {
182
+ return null;
183
+ }
184
+ }
185
+ if (!recordWithKeys(value, GENERATED_VIDEO_RECEIPT_KEYS)) return null;
186
+ if (
187
+ value.type !== "generated_video" ||
188
+ value.schemaVersion !== 1 ||
189
+ typeof value.operationId !== "string" ||
190
+ !UUID.test(value.operationId) ||
191
+ !recordWithKeys(value.video, GENERATED_VIDEO_FACT_KEYS)
192
+ ) {
193
+ return null;
194
+ }
195
+ const artifact = parseRetainedGeneratedVideoReference(value.artifact, expectedWorkspaceId);
196
+ const video = value.video as Record<string, unknown>;
197
+ if (
198
+ !artifact ||
199
+ typeof value.sandboxPath !== "string" ||
200
+ value.sandboxPath !==
201
+ `/workspace/generated-videos/generated-video-${artifact.artifactId}.mp4` ||
202
+ typeof video.durationSeconds !== "number" ||
203
+ !Number.isFinite(video.durationSeconds) ||
204
+ video.durationSeconds <= 0 ||
205
+ video.durationSeconds > 120 ||
206
+ video.width !== artifact.dimensions?.width ||
207
+ video.height !== artifact.dimensions?.height ||
208
+ typeof video.fps !== "number" ||
209
+ !Number.isFinite(video.fps) ||
210
+ video.fps <= 0 ||
211
+ video.fps > 120 ||
212
+ typeof video.hasAudio !== "boolean" ||
213
+ video.videoCodec !== "h264" ||
214
+ (video.audioCodec !== null && video.audioCodec !== "aac")
215
+ ) {
216
+ return null;
217
+ }
218
+ return value as GeneratedVideoReceipt;
219
+ }
220
+
221
+ /** Parse one closed terminal envelope; failures never fabricate an artifact. */
222
+ export function parseMediaGenerationResult(
223
+ value: unknown,
224
+ expectedWorkspaceId?: string,
225
+ ): MediaGenerationResult | null {
226
+ if (typeof value === "string" && value.length <= 12_000) {
227
+ try {
228
+ return parseMediaGenerationResult(JSON.parse(value), expectedWorkspaceId);
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
234
+ const result = value as Record<string, unknown>;
235
+ if (
236
+ result.type !== "media_generation_result" ||
237
+ result.schemaVersion !== 1 ||
238
+ typeof result.operationId !== "string" ||
239
+ !UUID.test(result.operationId)
240
+ ) {
241
+ return null;
242
+ }
243
+ if (result.status === "ready") {
244
+ if (!recordWithKeys(result, MEDIA_RESULT_READY_KEYS)) return null;
245
+ const receipt = parseGeneratedVideoReceipt(result.receipt, expectedWorkspaceId);
246
+ if (!receipt || receipt.operationId !== result.operationId) return null;
247
+ return value as MediaGenerationResult;
248
+ }
249
+ if (
250
+ !["provider_failed", "retention_failed", "cancelled_before_submit", "outcome_unknown"].includes(
251
+ String(result.status),
252
+ ) ||
253
+ !recordWithKeys(result, MEDIA_RESULT_FAILURE_KEYS) ||
254
+ typeof result.boundedPublicReason !== "string" ||
255
+ result.boundedPublicReason.length < 1 ||
256
+ result.boundedPublicReason.length > 1_000
257
+ ) {
258
+ return null;
259
+ }
260
+ return value as MediaGenerationResult;
261
+ }
262
+
263
+ const GENERATED_IMAGE_REFERENCE_KEYS = new Set([
264
+ "available",
265
+ "artifactId",
266
+ "kind",
267
+ "contentType",
268
+ "originalBytes",
269
+ "sha256",
270
+ "retainedAt",
271
+ "dimensions",
272
+ "retention",
273
+ "retrieval",
274
+ ]);
275
+ const DIMENSION_KEYS = new Set(["width", "height"]);
276
+ const RETENTION_KEYS = new Set(["policy", "expiresAt"]);
277
+ const RETRIEVAL_KEYS = new Set(["method", "path", "acceptRanges", "maxRangeBytes"]);
278
+ const GENERATED_IMAGE_RECEIPT_KEYS = new Set(["type", "artifact", "sandboxPath"]);
279
+ const GENERATED_VIDEO_RECEIPT_KEYS = new Set([
280
+ "type",
281
+ "schemaVersion",
282
+ "operationId",
283
+ "artifact",
284
+ "video",
285
+ "sandboxPath",
286
+ ]);
287
+ const GENERATED_VIDEO_FACT_KEYS = new Set([
288
+ "durationSeconds",
289
+ "width",
290
+ "height",
291
+ "fps",
292
+ "hasAudio",
293
+ "videoCodec",
294
+ "audioCodec",
295
+ ]);
296
+ const MEDIA_RESULT_READY_KEYS = new Set([
297
+ "type",
298
+ "schemaVersion",
299
+ "status",
300
+ "operationId",
301
+ "receipt",
302
+ ]);
303
+ const MEDIA_RESULT_FAILURE_KEYS = new Set([
304
+ "type",
305
+ "schemaVersion",
306
+ "status",
307
+ "operationId",
308
+ "boundedPublicReason",
309
+ ]);
310
+
311
+ function recordWithKeys(
312
+ value: unknown,
313
+ allowed: ReadonlySet<string>,
314
+ ): value is Record<string, unknown> {
315
+ return (
316
+ value !== null &&
317
+ typeof value === "object" &&
318
+ !Array.isArray(value) &&
319
+ Object.keys(value).length === allowed.size &&
320
+ Object.keys(value).every((key) => allowed.has(key))
321
+ );
322
+ }
323
+
324
+ function safeIntegerBetween(value: unknown, min: number, max: number): boolean {
325
+ return Number.isSafeInteger(value) && Number(value) >= min && Number(value) <= max;
326
+ }