@opengeni/react 3.5.1-canary.0 → 3.7.0-canary.1
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/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +248 -6
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-WY3MELVJ.js → chunk-5IXTJTK3.js} +1438 -397
- package/dist/chunk-5IXTJTK3.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-UNCXXIQR.js → chunk-INGB2N5R.js} +4310 -3982
- package/dist/chunk-INGB2N5R.js.map +1 -0
- package/dist/{chunk-TJFMMDQU.js → chunk-MOWYEBCQ.js} +1839 -17
- package/dist/chunk-MOWYEBCQ.js.map +1 -0
- package/dist/chunk-PAJU5KKL.js +331 -0
- package/dist/chunk-PAJU5KKL.js.map +1 -0
- package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
- package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
- package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
- package/dist/chunk-ZFOFY5ST.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/artifacts/index.d.ts +1 -1
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +46 -3
- package/dist/components/composer.d.ts +2 -0
- package/dist/components/session-chrome.d.ts +38 -5
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-conversation.d.ts +14 -0
- package/dist/components/timeline-anchor.d.ts +21 -0
- package/dist/composer.js +2 -3
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
- package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
- package/dist/hooks/use-file-attachments.d.ts +6 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +823 -965
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/lib/format.d.ts +5 -0
- package/dist/machines.js +3 -2
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +14 -4
- package/dist/session.js +19 -19
- package/dist/timeline/registry.d.ts +2 -0
- package/dist/timeline/types.d.ts +4 -2
- package/package.json +2 -2
- package/src/artifacts.ts +3 -0
- package/src/client.ts +2 -1
- package/src/components/artifacts/index.ts +3 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +307 -4
- package/src/components/composer.tsx +89 -25
- package/src/components/machine-input-display.ts +6 -0
- package/src/components/message-timeline.tsx +351 -204
- package/src/components/sandbox-workspace.tsx +11 -1
- package/src/components/session-chrome.tsx +501 -128
- package/src/components/session-commands-panel.tsx +113 -0
- package/src/components/session-conversation.tsx +141 -0
- package/src/components/timeline-anchor.tsx +91 -0
- package/src/conversation-timeline.ts +83 -0
- package/src/hooks/use-file-attachments.ts +34 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +21 -10
- package/src/hooks/use-session-events.ts +56 -39
- package/src/hooks/use-session.ts +23 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/index.ts +4 -0
- package/src/lib/format.ts +32 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/projection.ts +157 -19
- package/src/timeline/registry.ts +15 -3
- package/src/timeline/tool-renderers.tsx +117 -1
- package/src/timeline/types.ts +12 -1
- package/styles/compiled.css +495 -411
- package/styles/index.css +23 -0
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-IB27C53Y.js +0 -2582
- package/dist/chunk-IB27C53Y.js.map +0 -1
- package/dist/chunk-TJFMMDQU.js.map +0 -1
- package/dist/chunk-UNCXXIQR.js.map +0 -1
- package/dist/chunk-WY3MELVJ.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
package/dist/interaction.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useBrowserSession,
|
|
8
8
|
useBrowserSessions,
|
|
9
9
|
useSiteAuthConnections
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-24M4YBCL.js";
|
|
11
11
|
import {
|
|
12
12
|
ComputerViewer,
|
|
13
13
|
useComputerFrameStream,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import "./chunk-4MDLEHXL.js";
|
|
22
22
|
import "./chunk-5YK2ZDIG.js";
|
|
23
23
|
import "./chunk-JVYQRRC4.js";
|
|
24
|
+
import "./chunk-PLYRL5ER.js";
|
|
24
25
|
import "./chunk-KZ73RL76.js";
|
|
25
26
|
import {
|
|
26
27
|
useEmbeddedBrowserInteraction,
|
|
@@ -28,7 +29,6 @@ import {
|
|
|
28
29
|
useEmbeddedInteraction,
|
|
29
30
|
useEmbeddedInterventions
|
|
30
31
|
} from "./chunk-NEXLLTP4.js";
|
|
31
|
-
import "./chunk-VTOREEXF.js";
|
|
32
32
|
import "./chunk-22KP6LR5.js";
|
|
33
33
|
|
|
34
34
|
// src/hooks/use-network-routes.ts
|
package/dist/lib/format.d.ts
CHANGED
|
@@ -48,3 +48,8 @@ export declare function isCreditExhaustion(input: {
|
|
|
48
48
|
* debug surfaces.
|
|
49
49
|
*/
|
|
50
50
|
export declare function humanizeFailureReason(reason: string | null): string | null;
|
|
51
|
+
/** Project failure diagnostics without changing the stored event, including legacy retry wrappers. */
|
|
52
|
+
export declare function presentFailure(payload: Record<string, unknown>): {
|
|
53
|
+
reason: string | null;
|
|
54
|
+
safetyRefusal: boolean;
|
|
55
|
+
};
|
package/dist/machines.js
CHANGED
|
@@ -28,10 +28,11 @@ import {
|
|
|
28
28
|
healthPulses,
|
|
29
29
|
pointsFor,
|
|
30
30
|
useMachines
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ZFOFY5ST.js";
|
|
32
|
+
import "./chunk-PAJU5KKL.js";
|
|
33
|
+
import "./chunk-PLYRL5ER.js";
|
|
32
34
|
import "./chunk-KZ73RL76.js";
|
|
33
35
|
import "./chunk-NEXLLTP4.js";
|
|
34
|
-
import "./chunk-VTOREEXF.js";
|
|
35
36
|
import "./chunk-22KP6LR5.js";
|
|
36
37
|
export {
|
|
37
38
|
CONNECTION_STATUS_META,
|
package/dist/session-ui.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export type { UserMessageBodyProps } from "./components/user-message-body.js";
|
|
|
11
11
|
export { BUILT_IN_TURN_SUMMARY_FACET_IDS } from "./timeline/turn-summary.js";
|
|
12
12
|
export type { BuiltInTurnSummaryFacetId, TurnSummaryContext, TurnSummaryFacet, TurnSummaryFacetConfiguration, TurnSummaryFacetResult, TurnSummaryOptions, } from "./timeline/turn-summary.js";
|
|
13
13
|
export { QueueSurface } from "./components/queue-surface.js";
|
|
14
|
+
export { SessionConversation } from "./components/session-conversation.js";
|
|
15
|
+
export type { SessionConversationProps } from "./components/session-conversation.js";
|
|
14
16
|
export type { QueueSurfaceProps } from "./components/queue-surface.js";
|
|
15
17
|
export { SessionChrome, sessionChromeGoalPillExplanation, sessionChromeGoalPillLabel, sessionChromeGoalPillState, } from "./components/session-chrome.js";
|
|
16
18
|
export type { SessionChromeAgentsSignal, SessionChromeProps, SessionChromeSignalId, SessionChromeSignalTone, } from "./components/session-chrome.js";
|
|
19
|
+
export { SessionCommandsPanel } from "./components/session-commands-panel.js";
|
package/dist/session-ui.js
CHANGED
|
@@ -5,21 +5,29 @@ import {
|
|
|
5
5
|
MessageTimeline,
|
|
6
6
|
QueueSurface,
|
|
7
7
|
SessionChrome,
|
|
8
|
+
SessionCommandsPanel,
|
|
9
|
+
SessionConversation,
|
|
8
10
|
TimelineRow,
|
|
9
11
|
UserMessageBody,
|
|
10
12
|
sessionChromeGoalPillExplanation,
|
|
11
13
|
sessionChromeGoalPillLabel,
|
|
12
14
|
sessionChromeGoalPillState,
|
|
13
15
|
userMessageLikelyNeedsDisclosure
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-5IXTJTK3.js";
|
|
15
17
|
import "./chunk-W3OUAH6M.js";
|
|
18
|
+
import "./chunk-PAJU5KKL.js";
|
|
16
19
|
import "./chunk-JVYQRRC4.js";
|
|
17
|
-
import "./chunk-
|
|
20
|
+
import "./chunk-INGB2N5R.js";
|
|
21
|
+
import "./chunk-4JFUT22W.js";
|
|
18
22
|
import "./chunk-VZTQ73XT.js";
|
|
19
23
|
import {
|
|
20
24
|
createOlderHistoryLoadReceipt
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-MOWYEBCQ.js";
|
|
26
|
+
import "./chunk-IOLVN74D.js";
|
|
27
|
+
import "./chunk-PLYRL5ER.js";
|
|
28
|
+
import "./chunk-KZ73RL76.js";
|
|
29
|
+
import "./chunk-NEXLLTP4.js";
|
|
30
|
+
import "./chunk-WMGF4Z7X.js";
|
|
23
31
|
import "./chunk-22KP6LR5.js";
|
|
24
32
|
export {
|
|
25
33
|
BUILT_IN_TURN_SUMMARY_FACET_IDS,
|
|
@@ -28,6 +36,8 @@ export {
|
|
|
28
36
|
MessageTimeline,
|
|
29
37
|
QueueSurface,
|
|
30
38
|
SessionChrome,
|
|
39
|
+
SessionCommandsPanel,
|
|
40
|
+
SessionConversation,
|
|
31
41
|
TimelineRow,
|
|
32
42
|
UserMessageBody,
|
|
33
43
|
createOlderHistoryLoadReceipt,
|
package/dist/session.js
CHANGED
|
@@ -2,22 +2,33 @@ import {
|
|
|
2
2
|
approvalsFromRequiresAction,
|
|
3
3
|
createEmbeddedSessionClient,
|
|
4
4
|
isGoalEvent,
|
|
5
|
-
isHumanInputEvent,
|
|
6
5
|
isLineageRefreshEvent,
|
|
7
6
|
isSessionMcpApprovalPolicyEvent,
|
|
8
|
-
isTitleEvent,
|
|
9
|
-
isTurnQueueEvent,
|
|
10
7
|
projectPendingApprovals,
|
|
11
8
|
useFileAttachments,
|
|
12
9
|
useGoal,
|
|
10
|
+
useSessionControl,
|
|
11
|
+
useSessionLineage,
|
|
12
|
+
useSessionMcpApprovalPolicy
|
|
13
|
+
} from "./chunk-5PQJOCX5.js";
|
|
14
|
+
import {
|
|
15
|
+
buildTimeline,
|
|
16
|
+
createOlderHistoryLoadReceipt,
|
|
17
|
+
creditExhaustedFromEvents,
|
|
18
|
+
extractSessionRef,
|
|
19
|
+
groupTimeline,
|
|
20
|
+
humanInputRequestFromEvent,
|
|
21
|
+
isHumanInputEvent,
|
|
22
|
+
isTitleEvent,
|
|
23
|
+
isTurnQueueEvent,
|
|
24
|
+
projectPendingHumanInputRequests,
|
|
25
|
+
sessionStatusFromEvents,
|
|
26
|
+
toolDisplayName,
|
|
13
27
|
useHumanInputRequests,
|
|
14
28
|
useSession,
|
|
15
|
-
useSessionControl,
|
|
16
29
|
useSessionEvents,
|
|
17
|
-
useSessionLineage,
|
|
18
|
-
useSessionMcpApprovalPolicy,
|
|
19
30
|
useTurnQueue
|
|
20
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-MOWYEBCQ.js";
|
|
21
32
|
import {
|
|
22
33
|
FILE_ONLY_MESSAGE_TEXT,
|
|
23
34
|
composeSendInput,
|
|
@@ -25,20 +36,9 @@ import {
|
|
|
25
36
|
shouldSubmitOnKey,
|
|
26
37
|
useComposer
|
|
27
38
|
} from "./chunk-IOLVN74D.js";
|
|
39
|
+
import "./chunk-PLYRL5ER.js";
|
|
28
40
|
import "./chunk-KZ73RL76.js";
|
|
29
41
|
import "./chunk-NEXLLTP4.js";
|
|
30
|
-
import {
|
|
31
|
-
buildTimeline,
|
|
32
|
-
createOlderHistoryLoadReceipt,
|
|
33
|
-
creditExhaustedFromEvents,
|
|
34
|
-
extractSessionRef,
|
|
35
|
-
groupTimeline,
|
|
36
|
-
humanInputRequestFromEvent,
|
|
37
|
-
projectPendingHumanInputRequests,
|
|
38
|
-
sessionStatusFromEvents,
|
|
39
|
-
toolDisplayName
|
|
40
|
-
} from "./chunk-TJFMMDQU.js";
|
|
41
|
-
import "./chunk-VTOREEXF.js";
|
|
42
42
|
export {
|
|
43
43
|
FILE_ONLY_MESSAGE_TEXT,
|
|
44
44
|
approvalsFromRequiresAction,
|
|
@@ -22,6 +22,8 @@ export type ToolRegistryEntry = {
|
|
|
22
22
|
match: "name";
|
|
23
23
|
name: string;
|
|
24
24
|
render: ToolRenderer;
|
|
25
|
+
/** Disable untrusted `<server>__${name}` leaf matching for identity-sensitive renderers. */
|
|
26
|
+
matchPrefixedLeaf?: boolean | undefined;
|
|
25
27
|
};
|
|
26
28
|
export type ToolRegistry = {
|
|
27
29
|
/** Resolve the renderer for a call (never null — falls back to generic). */
|
package/dist/timeline/types.d.ts
CHANGED
|
@@ -228,7 +228,7 @@ export type FleetDecisionItem = {
|
|
|
228
228
|
policyVersion: "adaptive-shadow-v1";
|
|
229
229
|
actualOutcome: "selected" | "waiting" | "none";
|
|
230
230
|
actualCandidateKey: string | null;
|
|
231
|
-
actualReason: "lease_reused" | "pin" | "rotation" | "active" | "all_capped" | "none";
|
|
231
|
+
actualReason: "lease_reused" | "pin" | "rotation" | "active" | "all_capped" | "allocator_disabled" | "none";
|
|
232
232
|
shadowOutcome: "selected" | "paced" | "none";
|
|
233
233
|
shadowCandidateKey: string | null;
|
|
234
234
|
shadowReason: "fenced_in_flight" | "fenced_candidate_missing" | "admission_paced" | "no_eligible_candidate" | "overlay_isolated_empty" | "best_score" | "affinity_best" | "hysteresis_hold";
|
|
@@ -263,6 +263,8 @@ export type NoticeItem = {
|
|
|
263
263
|
id: string;
|
|
264
264
|
tone: "waiting" | "cancelled" | "failed" | "input";
|
|
265
265
|
text: string;
|
|
266
|
+
/** A preserved turn-end outcome, not a claim about current session state. */
|
|
267
|
+
recordedOutcome?: true;
|
|
266
268
|
/** Optional evidence kept inspectable without overwhelming the main rail. */
|
|
267
269
|
details?: {
|
|
268
270
|
label: string;
|
|
@@ -293,7 +295,7 @@ export type ContextCompactionItem = {
|
|
|
293
295
|
};
|
|
294
296
|
export type MachineInputMember = {
|
|
295
297
|
id: string;
|
|
296
|
-
kind: "scheduled_occurrence" | "goal_continuation" | "agent_message" | "agent_steer_instruction" | "child_terminal_result" | "media_generation_result" | "child_requires_action" | "child_requires_action_resolved" | "child_paused" | "child_waiting_capacity" | "child_progress";
|
|
298
|
+
kind: "scheduled_occurrence" | "goal_continuation" | "background_command_result" | "session_wait_timeout" | "agent_message" | "agent_steer_instruction" | "child_terminal_result" | "media_generation_result" | "child_requires_action" | "child_requires_action_resolved" | "child_paused" | "child_waiting_capacity" | "child_progress";
|
|
297
299
|
classification: "success" | "failure" | "action_required" | "info";
|
|
298
300
|
sourceId: string;
|
|
299
301
|
summary: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0-canary.1",
|
|
4
4
|
"description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"@dnd-kit/utilities": "3.2.2",
|
|
121
121
|
"@fontsource-variable/noto-sans-arabic": "5.2.10",
|
|
122
122
|
"@fontsource-variable/noto-sans-jp": "5.2.10",
|
|
123
|
-
"@opengeni/sdk": "^3.
|
|
123
|
+
"@opengeni/sdk": "^3.7.0-canary.1",
|
|
124
124
|
"clsx": "^2.1.1",
|
|
125
125
|
"dequal": "2.0.3",
|
|
126
126
|
"lucide-react": "^1.8.0",
|
package/src/artifacts.ts
CHANGED
|
@@ -18,6 +18,8 @@ export {
|
|
|
18
18
|
PresentationProjectionEditor,
|
|
19
19
|
PublishedHtmlArtifactFrame,
|
|
20
20
|
PUBLISHED_HTML_ARTIFACT_IFRAME_SANDBOX,
|
|
21
|
+
openGeniSiteBridgePortFromBootstrap,
|
|
22
|
+
publishedHtmlArtifactDocument,
|
|
21
23
|
SpreadsheetArtifactSurface,
|
|
22
24
|
SpreadsheetGrid,
|
|
23
25
|
SpreadsheetProjectionGrid,
|
|
@@ -62,6 +64,7 @@ export {
|
|
|
62
64
|
type PresentationProjectionConnector,
|
|
63
65
|
type PresentationProjectionEditorProps,
|
|
64
66
|
type PublishedHtmlArtifactFrameProps,
|
|
67
|
+
type PublishedHtmlArtifactToolBridge,
|
|
65
68
|
type PresentationProjectionElementMetadata,
|
|
66
69
|
type PresentationProjectionElement,
|
|
67
70
|
type PresentationProjectionFill,
|
package/src/client.ts
CHANGED
|
@@ -246,7 +246,8 @@ export type EmbeddedSessionLineageClientLike = EmbeddedSessionEventClientLike &
|
|
|
246
246
|
Pick<OpenGeniClient, "getSessionLineage">;
|
|
247
247
|
|
|
248
248
|
/** Exact client surface required by {@link useFileAttachments}. */
|
|
249
|
-
export type EmbeddedFileAttachmentClientLike = Pick<OpenGeniClient, "uploadFile"
|
|
249
|
+
export type EmbeddedFileAttachmentClientLike = Pick<OpenGeniClient, "uploadFile"> &
|
|
250
|
+
Partial<Pick<OpenGeniClient, "createFileDownloadUrl">>;
|
|
250
251
|
|
|
251
252
|
/** Exact client surface required by structured human-input hooks. */
|
|
252
253
|
export type EmbeddedHumanInputSessionClientLike = EmbeddedSessionEventClientLike &
|
|
@@ -6,7 +6,10 @@ export {
|
|
|
6
6
|
export {
|
|
7
7
|
PUBLISHED_HTML_ARTIFACT_IFRAME_SANDBOX,
|
|
8
8
|
PublishedHtmlArtifactFrame,
|
|
9
|
+
openGeniSiteBridgePortFromBootstrap,
|
|
10
|
+
publishedHtmlArtifactDocument,
|
|
9
11
|
type PublishedHtmlArtifactFrameProps,
|
|
12
|
+
type PublishedHtmlArtifactToolBridge,
|
|
10
13
|
} from "./published-html-artifact-frame";
|
|
11
14
|
export {
|
|
12
15
|
SpreadsheetArtifactSurface,
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
OPENGENI_SITE_BRIDGE_BOOTSTRAP_GLOBAL,
|
|
3
|
+
OPENGENI_SITE_BRIDGE_READY,
|
|
4
|
+
OPENGENI_SITE_BRIDGE_RESPONSE,
|
|
5
|
+
OPENGENI_SITE_BRIDGE_VERSION,
|
|
6
|
+
isOpenGeniSiteBridgeCancelMessage,
|
|
7
|
+
isOpenGeniSiteBridgeConnectMessage,
|
|
8
|
+
isOpenGeniSiteBridgeRequestMessage,
|
|
9
|
+
sanitizeOpenGeniSiteToolCallRequest,
|
|
10
|
+
isSiteHttpRequest,
|
|
11
|
+
serveSiteHttp,
|
|
12
|
+
type SiteHttpRequest,
|
|
13
|
+
type OpenGeniSiteBridgeResponseMessage,
|
|
14
|
+
type OpenGeniSiteToolCatalog,
|
|
15
|
+
} from "@opengeni/sdk/site";
|
|
16
|
+
import type {
|
|
17
|
+
ToolGatewayCallRequest,
|
|
18
|
+
ToolGatewayCallResponse,
|
|
19
|
+
ToolGatewayDeclarationsResponse,
|
|
20
|
+
} from "@opengeni/sdk";
|
|
21
|
+
import { useEffect, useRef, type CSSProperties } from "react";
|
|
2
22
|
|
|
3
23
|
export const PUBLISHED_HTML_ARTIFACT_IFRAME_SANDBOX = [
|
|
4
24
|
"allow-downloads",
|
|
@@ -17,22 +37,305 @@ export type PublishedHtmlArtifactFrameProps = {
|
|
|
17
37
|
title: string;
|
|
18
38
|
className?: string;
|
|
19
39
|
style?: CSSProperties;
|
|
40
|
+
toolBridge?: PublishedHtmlArtifactToolBridge;
|
|
20
41
|
};
|
|
21
42
|
|
|
43
|
+
export type PublishedHtmlArtifactToolBridge = {
|
|
44
|
+
fetch?: (request: SiteHttpRequest, signal: AbortSignal) => Promise<Response>;
|
|
45
|
+
catalog: (options: { signal: AbortSignal }) => Promise<OpenGeniSiteToolCatalog>;
|
|
46
|
+
call: (
|
|
47
|
+
request: ToolGatewayCallRequest,
|
|
48
|
+
options: { signal: AbortSignal },
|
|
49
|
+
) => Promise<ToolGatewayCallResponse>;
|
|
50
|
+
declarations?: (options: { signal: AbortSignal }) => Promise<ToolGatewayDeclarationsResponse>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const SITE_BRIDGE_BOOTSTRAP_SCRIPT = `<script>(()=>{const key=${JSON.stringify(
|
|
54
|
+
OPENGENI_SITE_BRIDGE_BOOTSTRAP_GLOBAL,
|
|
55
|
+
)};const parentWindow=window.parent;if(parentWindow===window)return;window.addEventListener("message",event=>{const data=event.data;if(event.source!==parentWindow||!data||data.type!==${JSON.stringify(
|
|
56
|
+
OPENGENI_SITE_BRIDGE_READY,
|
|
57
|
+
)}||data.version!==${OPENGENI_SITE_BRIDGE_VERSION}||event.ports.length!==1)return;const port=event.ports[0];const previous=window[key]?.port;if(previous&&previous!==port)previous.close();Object.defineProperty(window,key,{configurable:true,value:{port}});port.start();});})();</script>`;
|
|
58
|
+
|
|
59
|
+
export function publishedHtmlArtifactDocument(html: string, toolBridgeEnabled: boolean): string {
|
|
60
|
+
if (!toolBridgeEnabled) return html;
|
|
61
|
+
const doctypeEnd = leadingDoctypeEnd(html);
|
|
62
|
+
if (doctypeEnd >= 0) {
|
|
63
|
+
return `${html.slice(0, doctypeEnd + 1)}${SITE_BRIDGE_BOOTSTRAP_SCRIPT}${html.slice(doctypeEnd + 1)}`;
|
|
64
|
+
}
|
|
65
|
+
return `${SITE_BRIDGE_BOOTSTRAP_SCRIPT}${html}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function leadingDoctypeEnd(html: string): number {
|
|
69
|
+
const lower = html.toLowerCase();
|
|
70
|
+
let cursor = html.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
71
|
+
while (cursor < html.length) {
|
|
72
|
+
while (/\s/.test(html[cursor] ?? "")) cursor += 1;
|
|
73
|
+
if (!html.startsWith("<!--", cursor)) break;
|
|
74
|
+
const commentEnd = html.indexOf("-->", cursor + 4);
|
|
75
|
+
if (commentEnd < 0) return -1;
|
|
76
|
+
cursor = commentEnd + 3;
|
|
77
|
+
}
|
|
78
|
+
if (!lower.startsWith("<!doctype", cursor)) return -1;
|
|
79
|
+
return html.indexOf(">", cursor + 9);
|
|
80
|
+
}
|
|
81
|
+
|
|
22
82
|
/**
|
|
23
83
|
* Render exact published HTML in an opaque-origin iframe. Artifact scripts,
|
|
24
|
-
* external resources, forms, popups, and
|
|
25
|
-
* parent-origin authority, shared cookies/storage,
|
|
84
|
+
* external resources, forms, popups, downloads, and the host-filtered tool
|
|
85
|
+
* bridge work without granting parent-origin authority, shared cookies/storage,
|
|
86
|
+
* or top-level navigation.
|
|
26
87
|
*/
|
|
27
88
|
export function PublishedHtmlArtifactFrame(props: PublishedHtmlArtifactFrameProps) {
|
|
89
|
+
const frameRef = useRef<HTMLIFrameElement | null>(null);
|
|
90
|
+
const bridgeRef = useRef(props.toolBridge);
|
|
91
|
+
const onFrameLoadRef = useRef<() => void>(() => undefined);
|
|
92
|
+
const frameLoadPendingRef = useRef(false);
|
|
93
|
+
const toolBridgeEnabled = props.toolBridge !== undefined;
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
bridgeRef.current = props.toolBridge;
|
|
96
|
+
}, [props.toolBridge]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!toolBridgeEnabled || typeof window === "undefined") return;
|
|
99
|
+
const requests = new SiteBridgeRequestRegistry();
|
|
100
|
+
const attachToolPort = (data: unknown, ports: readonly MessagePort[]) => {
|
|
101
|
+
const port = openGeniSiteBridgePortFromBootstrap(data, ports);
|
|
102
|
+
if (!port) return;
|
|
103
|
+
requests.addPort(port);
|
|
104
|
+
port.addEventListener("message", (portEvent: MessageEvent<unknown>) => {
|
|
105
|
+
const message = portEvent.data;
|
|
106
|
+
if (isOpenGeniSiteBridgeCancelMessage(message)) {
|
|
107
|
+
requests.cancel(port, message.requestId);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (isSiteHttpRequest(message) && portEvent.ports.length === 1) {
|
|
111
|
+
const controller = requests.start(port, message.requestId);
|
|
112
|
+
if (!controller) {
|
|
113
|
+
portEvent.ports[0]!.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
void serveSiteHttp(
|
|
117
|
+
message,
|
|
118
|
+
portEvent.ports[0]!,
|
|
119
|
+
async (request, signal) => {
|
|
120
|
+
const fetch = bridgeRef.current?.fetch;
|
|
121
|
+
if (!fetch) throw new Error("This Site host does not support the session SDK");
|
|
122
|
+
return fetch(request, signal);
|
|
123
|
+
},
|
|
124
|
+
controller.signal,
|
|
125
|
+
).finally(() => requests.complete(port, message.requestId, controller));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!isOpenGeniSiteBridgeRequestMessage(message)) return;
|
|
129
|
+
const controller = requests.start(port, message.requestId);
|
|
130
|
+
if (!controller) {
|
|
131
|
+
port.postMessage({
|
|
132
|
+
type: OPENGENI_SITE_BRIDGE_RESPONSE,
|
|
133
|
+
version: OPENGENI_SITE_BRIDGE_VERSION,
|
|
134
|
+
requestId: message.requestId,
|
|
135
|
+
ok: false,
|
|
136
|
+
error: {
|
|
137
|
+
code: "duplicate_request_id",
|
|
138
|
+
message: "A Site tool request with this id is already running",
|
|
139
|
+
retryable: false,
|
|
140
|
+
},
|
|
141
|
+
} satisfies OpenGeniSiteBridgeResponseMessage);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
void handleSiteBridgeRequest(bridgeRef.current, message, controller.signal)
|
|
145
|
+
.then((value) => {
|
|
146
|
+
if (!controller.signal.aborted) {
|
|
147
|
+
port.postMessage({
|
|
148
|
+
type: OPENGENI_SITE_BRIDGE_RESPONSE,
|
|
149
|
+
version: OPENGENI_SITE_BRIDGE_VERSION,
|
|
150
|
+
requestId: message.requestId,
|
|
151
|
+
ok: true,
|
|
152
|
+
value,
|
|
153
|
+
} satisfies OpenGeniSiteBridgeResponseMessage);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.catch((error) => {
|
|
157
|
+
if (!controller.signal.aborted) {
|
|
158
|
+
port.postMessage({
|
|
159
|
+
type: OPENGENI_SITE_BRIDGE_RESPONSE,
|
|
160
|
+
version: OPENGENI_SITE_BRIDGE_VERSION,
|
|
161
|
+
requestId: message.requestId,
|
|
162
|
+
ok: false,
|
|
163
|
+
error: siteBridgeError(error),
|
|
164
|
+
} satisfies OpenGeniSiteBridgeResponseMessage);
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
.finally(() => requests.complete(port, message.requestId, controller));
|
|
168
|
+
});
|
|
169
|
+
port.addEventListener("messageerror", () => requests.closePort(port));
|
|
170
|
+
port.start();
|
|
171
|
+
port.postMessage({
|
|
172
|
+
type: OPENGENI_SITE_BRIDGE_READY,
|
|
173
|
+
version: OPENGENI_SITE_BRIDGE_VERSION,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const documentLease = new SiteBridgeDocumentLease(attachToolPort, () => requests.closeAll());
|
|
177
|
+
const onFrameLoad = () => {
|
|
178
|
+
frameLoadPendingRef.current = false;
|
|
179
|
+
const frameWindow = frameRef.current?.contentWindow ?? null;
|
|
180
|
+
if (!frameWindow) return;
|
|
181
|
+
documentLease.load(frameWindow);
|
|
182
|
+
};
|
|
183
|
+
onFrameLoadRef.current = onFrameLoad;
|
|
184
|
+
if (frameLoadPendingRef.current) {
|
|
185
|
+
frameLoadPendingRef.current = false;
|
|
186
|
+
onFrameLoad();
|
|
187
|
+
}
|
|
188
|
+
return () => {
|
|
189
|
+
onFrameLoadRef.current = () => undefined;
|
|
190
|
+
documentLease.close();
|
|
191
|
+
};
|
|
192
|
+
}, [props.html, toolBridgeEnabled]);
|
|
28
193
|
return (
|
|
29
194
|
<iframe
|
|
195
|
+
ref={frameRef}
|
|
30
196
|
title={props.title}
|
|
31
197
|
sandbox={PUBLISHED_HTML_ARTIFACT_IFRAME_SANDBOX}
|
|
32
198
|
referrerPolicy="no-referrer"
|
|
33
|
-
srcDoc={props.html}
|
|
199
|
+
srcDoc={publishedHtmlArtifactDocument(props.html, toolBridgeEnabled)}
|
|
200
|
+
onLoad={() => {
|
|
201
|
+
frameLoadPendingRef.current = true;
|
|
202
|
+
onFrameLoadRef.current();
|
|
203
|
+
}}
|
|
34
204
|
className={props.className}
|
|
35
205
|
style={props.style}
|
|
36
206
|
/>
|
|
37
207
|
);
|
|
38
208
|
}
|
|
209
|
+
|
|
210
|
+
export class SiteBridgeRequestRegistry {
|
|
211
|
+
private readonly controllersByPort = new Map<MessagePort, Map<string, AbortController>>();
|
|
212
|
+
|
|
213
|
+
addPort(port: MessagePort): void {
|
|
214
|
+
// Several SDK clients may share one document bootstrap. Document teardown,
|
|
215
|
+
// not another client connecting, revokes all of that document's ports.
|
|
216
|
+
if (!this.controllersByPort.has(port)) this.controllersByPort.set(port, new Map());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
start(port: MessagePort, requestId: string): AbortController | null {
|
|
220
|
+
const controllers = this.controllersByPort.get(port);
|
|
221
|
+
if (!controllers || controllers.has(requestId)) return null;
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
controllers.set(requestId, controller);
|
|
224
|
+
return controller;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
cancel(port: MessagePort, requestId: string): void {
|
|
228
|
+
const controllers = this.controllersByPort.get(port);
|
|
229
|
+
const controller = controllers?.get(requestId);
|
|
230
|
+
if (!controller) return;
|
|
231
|
+
controller.abort(new Error("Site tool request cancelled"));
|
|
232
|
+
controllers!.delete(requestId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
complete(port: MessagePort, requestId: string, controller: AbortController): void {
|
|
236
|
+
const controllers = this.controllersByPort.get(port);
|
|
237
|
+
if (controllers?.get(requestId) === controller) controllers.delete(requestId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
closePort(port: MessagePort): void {
|
|
241
|
+
const controllers = this.controllersByPort.get(port);
|
|
242
|
+
if (!controllers) return;
|
|
243
|
+
for (const controller of controllers.values()) {
|
|
244
|
+
controller.abort(new Error("Site tool bridge port closed"));
|
|
245
|
+
}
|
|
246
|
+
controllers.clear();
|
|
247
|
+
this.controllersByPort.delete(port);
|
|
248
|
+
port.close();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
closeAll(): void {
|
|
252
|
+
for (const port of [...this.controllersByPort.keys()]) this.closePort(port);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export class SiteBridgeDocumentLease {
|
|
257
|
+
private loaded = false;
|
|
258
|
+
private bootstrapPort: MessagePort | null = null;
|
|
259
|
+
|
|
260
|
+
constructor(
|
|
261
|
+
private readonly attachToolPort: (data: unknown, ports: readonly MessagePort[]) => void,
|
|
262
|
+
private readonly closeActivePorts: () => void,
|
|
263
|
+
private readonly createMessageChannel: () => MessageChannel = () => new MessageChannel(),
|
|
264
|
+
) {}
|
|
265
|
+
|
|
266
|
+
load(frameWindow: Pick<Window, "postMessage">): boolean {
|
|
267
|
+
if (this.loaded) {
|
|
268
|
+
this.close();
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
this.loaded = true;
|
|
272
|
+
const channel = this.createMessageChannel();
|
|
273
|
+
this.bootstrapPort = channel.port1;
|
|
274
|
+
channel.port1.addEventListener("message", (event: MessageEvent<unknown>) => {
|
|
275
|
+
this.attachToolPort(event.data, event.ports);
|
|
276
|
+
});
|
|
277
|
+
channel.port1.start();
|
|
278
|
+
frameWindow.postMessage(
|
|
279
|
+
{
|
|
280
|
+
type: OPENGENI_SITE_BRIDGE_READY,
|
|
281
|
+
version: OPENGENI_SITE_BRIDGE_VERSION,
|
|
282
|
+
},
|
|
283
|
+
"*",
|
|
284
|
+
[channel.port2],
|
|
285
|
+
);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
close(): void {
|
|
290
|
+
this.bootstrapPort?.close();
|
|
291
|
+
this.bootstrapPort = null;
|
|
292
|
+
this.closeActivePorts();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function openGeniSiteBridgePortFromBootstrap(
|
|
297
|
+
data: unknown,
|
|
298
|
+
ports: readonly MessagePort[],
|
|
299
|
+
): MessagePort | null {
|
|
300
|
+
if (!isOpenGeniSiteBridgeConnectMessage(data)) return null;
|
|
301
|
+
return ports.length === 1 ? ports[0]! : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function handleSiteBridgeRequest(
|
|
305
|
+
bridge: PublishedHtmlArtifactToolBridge | undefined,
|
|
306
|
+
message: Parameters<typeof isOpenGeniSiteBridgeRequestMessage>[0] & {
|
|
307
|
+
method: "catalog" | "call" | "declarations";
|
|
308
|
+
requestId: string;
|
|
309
|
+
payload?: ToolGatewayCallRequest;
|
|
310
|
+
},
|
|
311
|
+
signal: AbortSignal,
|
|
312
|
+
): Promise<OpenGeniSiteToolCatalog | ToolGatewayCallResponse | ToolGatewayDeclarationsResponse> {
|
|
313
|
+
if (!bridge) throw new Error("Site tool bridge is unavailable");
|
|
314
|
+
if (message.method === "catalog") return await bridge.catalog({ signal });
|
|
315
|
+
if (message.method === "declarations") {
|
|
316
|
+
if (!bridge.declarations) throw new Error("Site tool declarations are unavailable");
|
|
317
|
+
return await bridge.declarations({ signal });
|
|
318
|
+
}
|
|
319
|
+
return await bridge.call(sanitizeOpenGeniSiteToolCallRequest(message.payload!), { signal });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function siteBridgeError(error: unknown): {
|
|
323
|
+
code: string;
|
|
324
|
+
message: string;
|
|
325
|
+
retryable: boolean;
|
|
326
|
+
outcomeUnknown: boolean;
|
|
327
|
+
} {
|
|
328
|
+
const candidate = error as {
|
|
329
|
+
code?: unknown;
|
|
330
|
+
message?: unknown;
|
|
331
|
+
retryable?: unknown;
|
|
332
|
+
outcomeUnknown?: unknown;
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
code: typeof candidate?.code === "string" ? candidate.code : "site_tool_call_failed",
|
|
336
|
+
message:
|
|
337
|
+
typeof candidate?.message === "string" ? candidate.message : "Site tool request failed",
|
|
338
|
+
retryable: candidate?.retryable === true,
|
|
339
|
+
outcomeUnknown: candidate?.outcomeUnknown === true,
|
|
340
|
+
};
|
|
341
|
+
}
|