@datalayer/agent-runtimes 1.3.10 → 1.3.13
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/README.md +1 -2
- package/lib/AgentNode.js +3 -3
- package/lib/api/runtimes/environments.d.ts +22 -1
- package/lib/api/runtimes/environments.js +31 -0
- package/lib/api/runtimes/healthz.js +4 -2
- package/lib/api/spacer/healthz.js +4 -2
- package/lib/chat/ChatFloating.d.ts +8 -1
- package/lib/chat/ChatFloating.js +5 -2
- package/lib/chat/base/ChatBase.js +39 -25
- package/lib/chat/base/modelChoice.d.ts +62 -0
- package/lib/chat/base/modelChoice.js +56 -0
- package/lib/chat/display/ButtonGlow.d.ts +9 -0
- package/lib/chat/display/ButtonGlow.js +60 -0
- package/lib/chat/display/EmptyState.js +4 -1
- package/lib/chat/header/ChatHeaderBase.js +4 -56
- package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
- package/lib/chat/header/ChatViewModeToggle.js +55 -0
- package/lib/chat/index.d.ts +1 -0
- package/lib/chat/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.js +13 -5
- package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
- package/lib/chat/messages/FormSubmissionMessage.js +49 -0
- package/lib/chat/messages/formSubmission.d.ts +41 -0
- package/lib/chat/messages/formSubmission.js +90 -0
- package/lib/chat/prompt/menus/ModelSelector.js +11 -4
- package/lib/chat/styles/streamdownStyles.js +40 -0
- package/lib/client/mixins/RuntimesMixin.js +1 -1
- package/lib/client/mixins/SpacerMixin.js +3 -3
- package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
- package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
- package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
- package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
- package/lib/components/icons/ArtifactIcon.js +2 -1
- package/lib/examples/A2UiAgentExample.js +36 -223
- package/lib/examples/A2UiJupyterOutputExample.js +11 -1
- package/lib/examples/A2UiViewerExample.d.ts +9 -2
- package/lib/examples/A2UiViewerExample.js +33 -3
- package/lib/examples/DecksAgent.d.ts +1 -1
- package/lib/examples/DecksAgent.js +1 -1
- package/lib/examples/main.js +37 -35
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
- package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
- package/lib/loop/core/index.d.ts +44 -4
- package/lib/loop/core/index.js +29 -2
- package/lib/loop/embed/LoopEmbed.js +7 -0
- package/lib/loop/index.d.ts +1 -1
- package/lib/loop/index.js +4 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
- package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
- package/lib/loop/plugins/a2ui-surface/index.js +91 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
- package/lib/loop/plugins/chat/ChatView.js +95 -15
- package/lib/loop/plugins/chat/index.d.ts +14 -0
- package/lib/loop/plugins/chat/index.js +1 -0
- package/lib/loop/plugins/document/index.js +4 -0
- package/lib/loop/plugins/notebook/index.js +4 -0
- package/lib/loop/plugins/page-layout/index.d.ts +36 -0
- package/lib/loop/plugins/page-layout/index.js +87 -7
- package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
- package/lib/loop/presets.d.ts +19 -0
- package/lib/loop/presets.js +9 -1
- package/lib/models/Deck.d.ts +14 -0
- package/lib/models/Deck.js +5 -0
- package/lib/models/Environment.d.ts +117 -0
- package/lib/models/ItemType.d.ts +1 -1
- package/lib/models/Runtime.d.ts +6 -0
- package/lib/models/SpaceItem.d.ts +2 -1
- package/lib/models/environments.generated.d.ts +35 -1
- package/lib/models/environments.generated.js +1 -1
- package/lib/models/index.d.ts +1 -0
- package/lib/models/index.js +1 -0
- package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
- package/lib/protocols/BrowserAgentAdapter.js +11 -4
- package/lib/runtimes/actions.d.ts +5 -0
- package/lib/runtimes/actions.js +19 -0
- package/lib/specs/agents/agents.d.ts +3 -1
- package/lib/specs/agents/agents.js +426 -130
- package/lib/specs/frontendTools.d.ts +1 -0
- package/lib/specs/frontendTools.js +31 -0
- package/lib/specs/mcpServers.js +13 -13
- package/lib/specs/models.js +7 -7
- package/lib/specs/outputs.js +9 -0
- package/lib/specs/skills.js +7 -7
- package/lib/specs/teams/teams.js +1 -1
- package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
- package/lib/types/outputs.d.ts +2 -0
- package/package.json +10 -4
- package/patches/@primer+react+37.31.0.patch +390 -0
- package/patches/eslint-plugin-react+7.37.5.patch +65 -0
- package/scripts/apply-patches.sh +15 -1
- package/scripts/codegen/compose.py +4 -4
- package/scripts/codegen/generate_agents.py +1 -2
- package/scripts/codegen/generate_frontend_tools.py +1 -1
- package/scripts/codegen/generate_mcp_servers.py +2 -2
- package/scripts/codegen/generate_models.py +33 -8
- package/scripts/codegen/generate_outputs.py +2 -0
- package/scripts/codegen/generate_skills.py +2 -2
- package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Tooltip } from '@primer/react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
import { CommentDiscussionIcon, DeviceMobileIcon, GrabberIcon, SidebarExpandIcon, } from '@primer/octicons-react';
|
|
5
|
+
import { CHAT_VIEW_MODES, SIDEBAR_NEEDS_MOUNT_POINT } from '../viewModes';
|
|
6
|
+
/** The drawing of each mode. */
|
|
7
|
+
export const VIEW_MODE_ICONS = {
|
|
8
|
+
floating: CommentDiscussionIcon,
|
|
9
|
+
'floating-small': DeviceMobileIcon,
|
|
10
|
+
'floating-draggable': GrabberIcon,
|
|
11
|
+
sidebar: SidebarExpandIcon,
|
|
12
|
+
};
|
|
13
|
+
export function ChatViewModeToggle({ value, onChange, disabledModes = [], tooltipDirection = 'n', }) {
|
|
14
|
+
return (_jsx(Box, { role: "group", "aria-label": "Display mode", "data-chat-view-mode-toggle": "", sx: {
|
|
15
|
+
display: 'inline-flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
bg: 'neutral.muted',
|
|
18
|
+
borderRadius: '6px',
|
|
19
|
+
p: '2px',
|
|
20
|
+
gap: '1px',
|
|
21
|
+
}, children: CHAT_VIEW_MODES.map(({ mode, label }) => {
|
|
22
|
+
const ModeIcon = VIEW_MODE_ICONS[mode];
|
|
23
|
+
const selected = value === mode;
|
|
24
|
+
/* Greyed rather than left out: the person sees the mode exists, and
|
|
25
|
+
that this page has no place for it. */
|
|
26
|
+
const disabled = disabledModes.includes(mode);
|
|
27
|
+
return (_jsx(Tooltip, { text: disabled && mode === 'sidebar'
|
|
28
|
+
? `${label} — ${SIDEBAR_NEEDS_MOUNT_POINT}`
|
|
29
|
+
: label, direction: tooltipDirection, children: _jsx(Box, { as: "button", type: "button", "aria-label": label, "aria-pressed": selected, "aria-disabled": disabled || undefined, onClick: () => {
|
|
30
|
+
if (!disabled) {
|
|
31
|
+
onChange(mode);
|
|
32
|
+
}
|
|
33
|
+
}, sx: {
|
|
34
|
+
display: 'inline-flex',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
width: 26,
|
|
38
|
+
height: 24,
|
|
39
|
+
borderRadius: '4px',
|
|
40
|
+
border: 'none',
|
|
41
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
42
|
+
opacity: disabled ? 0.45 : 1,
|
|
43
|
+
bg: selected ? 'canvas.default' : 'transparent',
|
|
44
|
+
boxShadow: selected ? 'shadow.small' : 'none',
|
|
45
|
+
color: selected ? 'fg.default' : 'fg.muted',
|
|
46
|
+
transition: 'all 0.15s ease',
|
|
47
|
+
'&:hover': disabled
|
|
48
|
+
? {}
|
|
49
|
+
: {
|
|
50
|
+
color: 'fg.default',
|
|
51
|
+
bg: selected ? 'canvas.default' : 'neutral.subtle',
|
|
52
|
+
},
|
|
53
|
+
}, children: _jsx(ModeIcon, { size: 14 }) }) }, mode));
|
|
54
|
+
}) }));
|
|
55
|
+
}
|
package/lib/chat/index.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export { McpStatusIndicator, type McpStatusIndicatorProps, type McpServerStatus,
|
|
|
59
59
|
export { PoweredByTag, type PoweredByTagProps } from './display/PoweredByTag';
|
|
60
60
|
export { FloatingBrandButton, type FloatingBrandButtonProps, } from './display/FloatingBrandButton';
|
|
61
61
|
export { ChatHeader, type ChatHeaderProps } from './header/ChatHeader';
|
|
62
|
+
export { ChatViewModeToggle, VIEW_MODE_ICONS, type ChatViewModeToggleProps, } from './header/ChatViewModeToggle';
|
|
62
63
|
export { MessagePart, type MessagePartProps, TextPart, type TextPartProps, ReasoningPart, type ReasoningPartProps, ToolPart, type ToolPartProps, DynamicToolPart, type DynamicToolPartProps, } from './parts';
|
|
63
64
|
export { ToolCallDisplay, type ToolCallDisplayProps } from './tools';
|
|
64
65
|
export { ToolApprovalDialog, useToolApprovalDialog, type ToolApprovalDialogProps, } from './tools/ToolApprovalDialog';
|
package/lib/chat/index.js
CHANGED
|
@@ -68,6 +68,7 @@ export { McpStatusIndicator, SandboxStatusIndicator, } from './indicators';
|
|
|
68
68
|
export { PoweredByTag } from './display/PoweredByTag';
|
|
69
69
|
export { FloatingBrandButton, } from './display/FloatingBrandButton';
|
|
70
70
|
export { ChatHeader } from './header/ChatHeader';
|
|
71
|
+
export { ChatViewModeToggle, VIEW_MODE_ICONS, } from './header/ChatViewModeToggle';
|
|
71
72
|
// Components - Message part renderers
|
|
72
73
|
export { MessagePart, TextPart, ReasoningPart, ToolPart, DynamicToolPart, } from './parts';
|
|
73
74
|
// Components - Tool UI
|
|
@@ -18,6 +18,8 @@ import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/
|
|
|
18
18
|
import { ToolCallDisplay } from '../tools/ToolCallDisplay';
|
|
19
19
|
import { TurnFooter } from './TurnFooter';
|
|
20
20
|
import { normalizeAssistantMarkdown } from './assistantMarkdown';
|
|
21
|
+
import { parseFormSubmission } from './formSubmission';
|
|
22
|
+
import { FormSubmissionMessage } from './FormSubmissionMessage';
|
|
21
23
|
import { isToolCallMessage, getMessageText } from '../../utils';
|
|
22
24
|
import { A2AAgentDialog, a2aAgentDetails } from '../tools/A2AAgentDialog';
|
|
23
25
|
import { SUBAGENT_STOPPED } from '../../types/stream';
|
|
@@ -697,11 +699,17 @@ export function ChatMessageList({ displayItems, isLoading, isStreaming, showLoad
|
|
|
697
699
|
display: 'inline-flex',
|
|
698
700
|
gap: 1,
|
|
699
701
|
alignItems: 'center',
|
|
700
|
-
}, children: [_jsx(ToolsIcon, { size: 10 }), " ", label] }) }, `${label}-${chipIndex}`))) })) : null, isUser ? (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
702
|
+
}, children: [_jsx(ToolsIcon, { size: 10 }), " ", label] }) }, `${label}-${chipIndex}`))) })) : null, isUser ? ((() => {
|
|
703
|
+
// A submitted form is drawn as the form, not as the JSON
|
|
704
|
+
// the agent reads — see `formSubmission`.
|
|
705
|
+
const text = getMessageText(message);
|
|
706
|
+
const submission = parseFormSubmission(text);
|
|
707
|
+
return submission ? (_jsx(FormSubmissionMessage, { submission: submission })) : (_jsx(Text, { sx: {
|
|
708
|
+
fontSize: 1,
|
|
709
|
+
whiteSpace: 'pre-wrap',
|
|
710
|
+
wordBreak: 'break-word',
|
|
711
|
+
}, children: text }));
|
|
712
|
+
})()) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: normalizeAssistantMarkdown(getMessageText(message) || (isStreaming ? '...' : '')) }) }))] }), renderMessageFooter ? (_jsx(Box, { sx: {
|
|
705
713
|
display: 'flex',
|
|
706
714
|
gap: 2,
|
|
707
715
|
alignItems: 'center',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A submitted form, as a card in the transcript.
|
|
3
|
+
*
|
|
4
|
+
* Drawn where the reader's turn would be, in the shape a tool call has: a
|
|
5
|
+
* header that names the form and says how many fields it carried, a chevron
|
|
6
|
+
* that opens it, and the fields as a two-column table underneath. The JSON
|
|
7
|
+
* the agent reads stays in the turn's text; the reader sees the form they
|
|
8
|
+
* filled in.
|
|
9
|
+
*
|
|
10
|
+
* @module chat/messages/FormSubmissionMessage
|
|
11
|
+
*/
|
|
12
|
+
import type { JSX } from 'react';
|
|
13
|
+
import { type FormSubmission } from './formSubmission';
|
|
14
|
+
export declare function FormSubmissionMessage({ submission, }: {
|
|
15
|
+
submission: FormSubmission;
|
|
16
|
+
}): JSX.Element;
|
|
17
|
+
export default FormSubmissionMessage;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { ChecklistIcon, ChevronDownIcon, ChevronRightIcon, } from '@primer/octicons-react';
|
|
6
|
+
import { displayValue, labelOfFieldId, } from './formSubmission';
|
|
7
|
+
export function FormSubmissionMessage({ submission, }) {
|
|
8
|
+
const [expanded, setExpanded] = useState(true);
|
|
9
|
+
const entries = Object.entries(submission.values);
|
|
10
|
+
const count = entries.length;
|
|
11
|
+
return (_jsxs(Box, { sx: {
|
|
12
|
+
width: '100%',
|
|
13
|
+
minWidth: 280,
|
|
14
|
+
border: '1px solid',
|
|
15
|
+
borderColor: 'border.default',
|
|
16
|
+
borderRadius: '12px',
|
|
17
|
+
overflow: 'hidden',
|
|
18
|
+
backgroundColor: 'canvas.default',
|
|
19
|
+
color: 'fg.default',
|
|
20
|
+
}, children: [_jsxs(Box, { as: "button", type: "button", "aria-expanded": expanded, onClick: () => setExpanded(value => !value), sx: {
|
|
21
|
+
width: '100%',
|
|
22
|
+
display: 'flex',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
gap: 2,
|
|
25
|
+
padding: 2,
|
|
26
|
+
backgroundColor: 'canvas.subtle',
|
|
27
|
+
border: 'none',
|
|
28
|
+
borderBottom: expanded ? '1px solid' : 'none',
|
|
29
|
+
borderColor: 'border.default',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
textAlign: 'left',
|
|
32
|
+
color: 'fg.default',
|
|
33
|
+
'&:hover': { backgroundColor: 'neutral.muted' },
|
|
34
|
+
}, children: [_jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0 }, children: expanded ? (_jsx(ChevronDownIcon, { size: 16 })) : (_jsx(ChevronRightIcon, { size: 16 })) }), _jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0 }, children: _jsx(ChecklistIcon, { size: 16 }) }), _jsxs(Text, { sx: { fontWeight: 'semibold', fontSize: 1 }, children: ["Submitted ", submission.title] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', ml: 'auto' }, children: [count, " field", count === 1 ? '' : 's'] })] }), expanded ? (_jsx(Box, { as: "table", sx: { width: '100%', borderCollapse: 'collapse' }, children: _jsx(Box, { as: "tbody", children: entries.map(([id, value]) => (_jsxs(Box, { as: "tr", sx: {
|
|
35
|
+
'& + tr td': {
|
|
36
|
+
borderTop: '1px solid',
|
|
37
|
+
borderColor: 'border.muted',
|
|
38
|
+
},
|
|
39
|
+
}, children: [_jsx(Box, { as: "td", sx: {
|
|
40
|
+
px: 3,
|
|
41
|
+
py: 1,
|
|
42
|
+
fontSize: 0,
|
|
43
|
+
color: 'fg.muted',
|
|
44
|
+
whiteSpace: 'nowrap',
|
|
45
|
+
verticalAlign: 'top',
|
|
46
|
+
width: '40%',
|
|
47
|
+
}, children: labelOfFieldId(id) }), _jsx(Box, { as: "td", sx: { px: 3, py: 1, fontSize: 1, wordBreak: 'break-word' }, children: displayValue(value) })] }, id))) }) })) : null] }));
|
|
48
|
+
}
|
|
49
|
+
export default FormSubmissionMessage;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A form submission as a chat turn: written for the agent, drawn for the
|
|
3
|
+
* reader.
|
|
4
|
+
*
|
|
5
|
+
* When a reader submits a surface the agent drew, the values go back to the
|
|
6
|
+
* agent as the reader's next turn — that is how the confirmation lands in
|
|
7
|
+
* the conversation. But a turn is text, and the reader was shown that text:
|
|
8
|
+
* a JSON blob under "here are the values", which is the agent's reading and
|
|
9
|
+
* nobody else's. So the turn carries the submission in a fenced block of its
|
|
10
|
+
* own language, `a2ui-submission`. The agent reads the JSON inside it as
|
|
11
|
+
* before; the transcript recognises the fence and draws a card instead —
|
|
12
|
+
* the form's name and its fields, the way a tool call is drawn.
|
|
13
|
+
*
|
|
14
|
+
* Pure, so the two halves — the sender's writing, the transcript's reading —
|
|
15
|
+
* are tested together without a chat.
|
|
16
|
+
*
|
|
17
|
+
* @module chat/messages/formSubmission
|
|
18
|
+
*/
|
|
19
|
+
/** The fence's language: what marks a turn as a submission. */
|
|
20
|
+
export declare const FORM_SUBMISSION_LANGUAGE = "a2ui-submission";
|
|
21
|
+
export type FormSubmission = {
|
|
22
|
+
/** The form's title, as the surface named it. */
|
|
23
|
+
title: string;
|
|
24
|
+
/** The surface the values came from, when known. */
|
|
25
|
+
surfaceId?: string;
|
|
26
|
+
/** The values, by field id. */
|
|
27
|
+
values: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
/** The turn to send: a sentence, the fenced submission, the ask. */
|
|
30
|
+
export declare function encodeFormSubmission(submission: FormSubmission): string;
|
|
31
|
+
/** The submission a turn carries, or `null` for any other turn. */
|
|
32
|
+
export declare function parseFormSubmission(text: string): FormSubmission | null;
|
|
33
|
+
/**
|
|
34
|
+
* A field id as a label: `party-size` → `Party size`.
|
|
35
|
+
*
|
|
36
|
+
* The turn carries ids, not labels — the values are keyed by them — and an
|
|
37
|
+
* id is a readable enough label once its dashes and underscores are spaces.
|
|
38
|
+
*/
|
|
39
|
+
export declare function labelOfFieldId(id: string): string;
|
|
40
|
+
/** A value as a reader would write it; `—` for nothing. */
|
|
41
|
+
export declare function displayValue(value: unknown): string;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A form submission as a chat turn: written for the agent, drawn for the
|
|
7
|
+
* reader.
|
|
8
|
+
*
|
|
9
|
+
* When a reader submits a surface the agent drew, the values go back to the
|
|
10
|
+
* agent as the reader's next turn — that is how the confirmation lands in
|
|
11
|
+
* the conversation. But a turn is text, and the reader was shown that text:
|
|
12
|
+
* a JSON blob under "here are the values", which is the agent's reading and
|
|
13
|
+
* nobody else's. So the turn carries the submission in a fenced block of its
|
|
14
|
+
* own language, `a2ui-submission`. The agent reads the JSON inside it as
|
|
15
|
+
* before; the transcript recognises the fence and draws a card instead —
|
|
16
|
+
* the form's name and its fields, the way a tool call is drawn.
|
|
17
|
+
*
|
|
18
|
+
* Pure, so the two halves — the sender's writing, the transcript's reading —
|
|
19
|
+
* are tested together without a chat.
|
|
20
|
+
*
|
|
21
|
+
* @module chat/messages/formSubmission
|
|
22
|
+
*/
|
|
23
|
+
/** The fence's language: what marks a turn as a submission. */
|
|
24
|
+
export const FORM_SUBMISSION_LANGUAGE = 'a2ui-submission';
|
|
25
|
+
const FENCE = new RegExp('```' + FORM_SUBMISSION_LANGUAGE + '\\s*\\n([\\s\\S]*?)\\n```');
|
|
26
|
+
/** The turn to send: a sentence, the fenced submission, the ask. */
|
|
27
|
+
export function encodeFormSubmission(submission) {
|
|
28
|
+
const json = JSON.stringify(submission, null, 2);
|
|
29
|
+
return (`I just submitted "${submission.title}".\n\n` +
|
|
30
|
+
'```' +
|
|
31
|
+
FORM_SUBMISSION_LANGUAGE +
|
|
32
|
+
'\n' +
|
|
33
|
+
json +
|
|
34
|
+
'\n```\n\n' +
|
|
35
|
+
'Please confirm you received it and briefly say what happens next.');
|
|
36
|
+
}
|
|
37
|
+
/** The submission a turn carries, or `null` for any other turn. */
|
|
38
|
+
export function parseFormSubmission(text) {
|
|
39
|
+
const match = FENCE.exec(text);
|
|
40
|
+
if (!match) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(match[1]);
|
|
45
|
+
if (!parsed ||
|
|
46
|
+
typeof parsed !== 'object' ||
|
|
47
|
+
typeof parsed.title !== 'string' ||
|
|
48
|
+
!parsed.values ||
|
|
49
|
+
typeof parsed.values !== 'object') {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
title: parsed.title,
|
|
54
|
+
...(parsed.surfaceId ? { surfaceId: String(parsed.surfaceId) } : {}),
|
|
55
|
+
values: parsed.values,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A field id as a label: `party-size` → `Party size`.
|
|
64
|
+
*
|
|
65
|
+
* The turn carries ids, not labels — the values are keyed by them — and an
|
|
66
|
+
* id is a readable enough label once its dashes and underscores are spaces.
|
|
67
|
+
*/
|
|
68
|
+
export function labelOfFieldId(id) {
|
|
69
|
+
const words = id.replace(/[-_]+/g, ' ').trim();
|
|
70
|
+
return words ? words[0].toUpperCase() + words.slice(1) : id;
|
|
71
|
+
}
|
|
72
|
+
/** A value as a reader would write it; `—` for nothing. */
|
|
73
|
+
export function displayValue(value) {
|
|
74
|
+
if (value === null || value === undefined) {
|
|
75
|
+
return '—';
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === 'string') {
|
|
78
|
+
return value.trim() === '' ? '—' : value;
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
return value.length === 0 ? '—' : value.map(displayValue).join(', ');
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === 'boolean') {
|
|
84
|
+
return value ? 'Yes' : 'No';
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === 'number') {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify(value);
|
|
90
|
+
}
|
|
@@ -13,15 +13,22 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
13
13
|
import { AiModelIcon } from '@primer/octicons-react';
|
|
14
14
|
export function ModelSelector({ models, selectedModel, onModelSelect, isA2AProtocol, }) {
|
|
15
15
|
const active = models.find(model => model.id === selectedModel);
|
|
16
|
+
/*
|
|
17
|
+
* A count, not a name — the same trigger as tools, skills, agents and
|
|
18
|
+
* suggestions beside it. The name is the longest label on the row and it
|
|
19
|
+
* changes width as the model changes, which pushed everything beside it
|
|
20
|
+
* about; the tooltip says which one, and how many there are to choose from.
|
|
21
|
+
*/
|
|
22
|
+
const summary = isA2AProtocol
|
|
23
|
+
? 'set by the agent config'
|
|
24
|
+
: `${active?.name ?? 'none selected'} · ${models.length} to choose from`;
|
|
16
25
|
return (_jsxs(Box, { sx: {
|
|
17
26
|
display: 'flex',
|
|
18
27
|
flexDirection: 'column',
|
|
19
28
|
alignItems: 'flex-end',
|
|
20
|
-
}, children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Tooltip, { text: isA2AProtocol
|
|
21
|
-
? 'This protocol does not take a model override'
|
|
22
|
-
: `Model — ${active?.name ?? 'none selected'}`, direction: "n", children: _jsx(Button, { type: "button", variant: "invisible", size: "small", "aria-label": `Model — ${active?.name ?? 'none selected'}`, leadingVisual: AiModelIcon, disabled: isA2AProtocol, sx: isA2AProtocol
|
|
29
|
+
}, children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Tooltip, { text: `Model — ${summary}`, direction: "n", children: _jsx(Button, { type: "button", variant: "invisible", size: "small", "aria-label": `Model — ${summary}`, leadingVisual: AiModelIcon, disabled: isA2AProtocol, sx: isA2AProtocol
|
|
23
30
|
? { opacity: 0.5, cursor: 'not-allowed' }
|
|
24
|
-
: undefined }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "end", children: _jsx(ActionList, { selectionVariant: "single", children: models.map(modelItem => (_jsxs(ActionList.Item, { selected: selectedModel === modelItem.id, onSelect: () => onModelSelect(modelItem.id), disabled: modelItem.isAvailable === false || isA2AProtocol, sx: modelItem.isAvailable === false
|
|
31
|
+
: undefined, children: _jsx(Text, { sx: { fontSize: 0 }, children: models.length }) }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "end", children: _jsx(ActionList, { selectionVariant: "single", children: models.map(modelItem => (_jsxs(ActionList.Item, { selected: selectedModel === modelItem.id, onSelect: () => onModelSelect(modelItem.id), disabled: modelItem.isAvailable === false || isA2AProtocol, sx: modelItem.isAvailable === false
|
|
25
32
|
? { color: 'fg.muted' }
|
|
26
33
|
: undefined, children: [modelItem.name, modelItem.isAvailable === false && (_jsx(ActionList.Description, { variant: "block", children: modelItem.unavailableReason ?? 'Missing API key' }))] }, modelItem.id))) }) })] }), isA2AProtocol && (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg', mt: 1 }, children: "A2A: Model set by agent config" }))] }));
|
|
27
34
|
}
|
|
@@ -250,6 +250,46 @@ export const streamdownMarkdownStyles = {
|
|
|
250
250
|
marginTop: '1em',
|
|
251
251
|
marginBottom: '1em',
|
|
252
252
|
},
|
|
253
|
+
/*
|
|
254
|
+
* The controls Streamdown draws over a table, an image and a diagram: copy
|
|
255
|
+
* and download, and the download's own menu.
|
|
256
|
+
*
|
|
257
|
+
* They are plain `<button>`s with Tailwind classes, and Tailwind's preflight
|
|
258
|
+
* — which we do not load — is what strips a button of its default chrome.
|
|
259
|
+
* Without it the browser drew them as it draws any button: a grey face and
|
|
260
|
+
* a bevelled border, in a message whose ground is the theme's. The reset
|
|
261
|
+
* here is the one the code block's header already has, and the colours are
|
|
262
|
+
* Primer's, so the row reads as part of the message in either colour mode.
|
|
263
|
+
*/
|
|
264
|
+
'& [data-streamdown="table-wrapper"] button, & [data-streamdown="image-wrapper"] button, & [data-streamdown="mermaid-block"] button': {
|
|
265
|
+
background: 'transparent',
|
|
266
|
+
border: 'none',
|
|
267
|
+
font: 'inherit',
|
|
268
|
+
cursor: 'pointer',
|
|
269
|
+
color: 'fg.muted',
|
|
270
|
+
borderRadius: '4px',
|
|
271
|
+
'&:hover': {
|
|
272
|
+
backgroundColor: 'neutral.muted',
|
|
273
|
+
color: 'fg.default',
|
|
274
|
+
},
|
|
275
|
+
'&:disabled': { cursor: 'not-allowed', opacity: 0.5 },
|
|
276
|
+
},
|
|
277
|
+
'& [data-streamdown="table-wrapper"] > .flex': {
|
|
278
|
+
marginBottom: '4px',
|
|
279
|
+
},
|
|
280
|
+
// The download menu, and its rows: the popover's own ground and rule, not
|
|
281
|
+
// the button reset above.
|
|
282
|
+
'& [data-streamdown="table-wrapper"] .bg-background, & [data-streamdown="mermaid-block"] .bg-background': {
|
|
283
|
+
backgroundColor: 'canvas.overlay',
|
|
284
|
+
borderColor: 'border.default',
|
|
285
|
+
color: 'fg.default',
|
|
286
|
+
boxShadow: 'shadow.medium',
|
|
287
|
+
},
|
|
288
|
+
'& [data-streamdown="table-wrapper"] .bg-background button, & [data-streamdown="mermaid-block"] .bg-background button': {
|
|
289
|
+
color: 'fg.default',
|
|
290
|
+
borderRadius: 0,
|
|
291
|
+
'&:hover': { backgroundColor: 'neutral.muted' },
|
|
292
|
+
},
|
|
253
293
|
// Include Tailwind utilities for Streamdown output
|
|
254
294
|
...tailwindUtilities,
|
|
255
295
|
};
|
|
@@ -246,7 +246,7 @@ export function RuntimesMixin(Base) {
|
|
|
246
246
|
async checkRuntimesHealth() {
|
|
247
247
|
const startTime = Date.now();
|
|
248
248
|
const errors = [];
|
|
249
|
-
let status
|
|
249
|
+
let status;
|
|
250
250
|
let healthy = false;
|
|
251
251
|
try {
|
|
252
252
|
// Test basic connectivity by listing environments (lightweight operation)
|
|
@@ -244,9 +244,9 @@ export function SpacerMixin(Base) {
|
|
|
244
244
|
// If getItem throws (e.g., 404), wrap in descriptive error
|
|
245
245
|
if (error.message?.includes('404') ||
|
|
246
246
|
error.message?.includes('not found')) {
|
|
247
|
-
throw new Error(`Failed to delete space item '${itemId}': Item not found
|
|
247
|
+
throw new Error(`Failed to delete space item '${itemId}': Item not found`, { cause: error });
|
|
248
248
|
}
|
|
249
|
-
throw new Error(`Failed to delete space item '${itemId}': ${error.message}
|
|
249
|
+
throw new Error(`Failed to delete space item '${itemId}': ${error.message}`, { cause: error });
|
|
250
250
|
}
|
|
251
251
|
// Item exists, proceed with deletion
|
|
252
252
|
const response = await items.deleteItem(token, itemId, spacerUrl);
|
|
@@ -291,7 +291,7 @@ export function SpacerMixin(Base) {
|
|
|
291
291
|
async checkSpacerHealth() {
|
|
292
292
|
const startTime = Date.now();
|
|
293
293
|
const errors = [];
|
|
294
|
-
let status
|
|
294
|
+
let status;
|
|
295
295
|
let healthy = false;
|
|
296
296
|
try {
|
|
297
297
|
// Test basic connectivity by getting user spaces (lightweight operation)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Provides the A2UI v0.9 markdown renderer to every `A2uiSurface` beneath
|
|
4
|
+
* it. Without it, `Text` components fall back to the raw markdown string
|
|
5
|
+
* (`## Title` instead of a rendered heading).
|
|
6
|
+
*
|
|
7
|
+
* In the library rather than the examples: the loop's own surface plugin
|
|
8
|
+
* draws surfaces, and needs the same context the examples had.
|
|
9
|
+
*/
|
|
10
|
+
export declare const A2uiMarkdownProvider: React.FC<React.PropsWithChildren>;
|
|
11
|
+
export default A2uiMarkdownProvider;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { MarkdownContext } from '@a2ui/react/v0_9';
|
|
3
|
+
import { renderMarkdown } from '@a2ui/markdown-it';
|
|
4
|
+
/**
|
|
5
|
+
* Provides the A2UI v0.9 markdown renderer to every `A2uiSurface` beneath
|
|
6
|
+
* it. Without it, `Text` components fall back to the raw markdown string
|
|
7
|
+
* (`## Title` instead of a rendered heading).
|
|
8
|
+
*
|
|
9
|
+
* In the library rather than the examples: the loop's own surface plugin
|
|
10
|
+
* draws surfaces, and needs the same context the examples had.
|
|
11
|
+
*/
|
|
12
|
+
export const A2uiMarkdownProvider = ({ children, }) => (_jsx(MarkdownContext.Provider, { value: renderMarkdown, children: children }));
|
|
13
|
+
export default A2uiMarkdownProvider;
|
|
@@ -62,6 +62,14 @@ export interface ICodeSandboxLauncherProps {
|
|
|
62
62
|
* Optional submit button label override.
|
|
63
63
|
*/
|
|
64
64
|
submitLabel?: string;
|
|
65
|
+
/**
|
|
66
|
+
* A published environment to offer and select: the version uid of its
|
|
67
|
+
* publication, as the Library page that sent the reader here names it.
|
|
68
|
+
* Nobody's listing holds another account's environment, so it is read from
|
|
69
|
+
* the publication, offered under *Library* and priced by the rate the
|
|
70
|
+
* publication carries (D-12, E2-19).
|
|
71
|
+
*/
|
|
72
|
+
libraryVersionUid?: string;
|
|
65
73
|
}
|
|
66
74
|
/**
|
|
67
75
|
* The launcher of a new code sandbox.
|
|
@@ -10,7 +10,8 @@ import { NO_RUNTIME_AVAILABLE_LABEL } from '@datalayer/core/lib/i18n';
|
|
|
10
10
|
import { isCodeSandboxProviderAvailable } from './codeSandboxProviders';
|
|
11
11
|
import { CodeSandboxVariant, codeSandboxVariantOf, codeSandboxVariantTitle, } from '../../models/CodeSandboxVariant';
|
|
12
12
|
import { CodeSandboxEnvironmentSelect, } from './CodeSandboxEnvironmentSelect';
|
|
13
|
-
import { codeSandboxEnvironmentOptions, codeSandboxEnvironmentVersion, firstLaunchableCodeSandboxEnvironment, } from './codeSandboxEnvironments';
|
|
13
|
+
import { codeSandboxEnvironmentOptions, codeSandboxEnvironmentVersion, firstLaunchableCodeSandboxEnvironment, libraryEnvironmentOf, } from './codeSandboxEnvironments';
|
|
14
|
+
import { getEnvironmentPublication } from '../../api/runtimes/environments';
|
|
14
15
|
import { getCodeSandboxGivenName, listCodeSandboxGivenNames, nextCodeSandboxGivenName, setCodeSandboxGivenName, } from './CodeSandboxNames';
|
|
15
16
|
import { iamStore, runtimesStore, useCoreStore, useIAMStore, } from '../../state/substates';
|
|
16
17
|
import { createRuntime as createRuntimeRecord } from '../../runtimes/actions';
|
|
@@ -42,7 +43,7 @@ const NOT_AVAILABLE_RETRIES = 5;
|
|
|
42
43
|
* The launcher of a new code sandbox.
|
|
43
44
|
*/
|
|
44
45
|
export function CodeSandboxLauncher(props) {
|
|
45
|
-
const { dialogTitle, kernelSnapshot, manager, onSubmit, markdownParser, sanitizer, upgradeSubscription, usageRoute = '/plans', submitLabel, startRuntime = true, } = props;
|
|
46
|
+
const { dialogTitle, kernelSnapshot, manager, onSubmit, markdownParser, sanitizer, upgradeSubscription, usageRoute = '/plans', submitLabel, startRuntime = true, libraryVersionUid, } = props;
|
|
46
47
|
const hasExample = startRuntime === 'with-example';
|
|
47
48
|
const shouldStartRuntime = startRuntime !== 'defer';
|
|
48
49
|
const user = iamStore.getState().user;
|
|
@@ -56,7 +57,45 @@ export function CodeSandboxLauncher(props) {
|
|
|
56
57
|
* below.
|
|
57
58
|
*/
|
|
58
59
|
const jupyterAvailable = isCodeSandboxProviderAvailable('local', runtimesStore.getState().multiServiceManager);
|
|
59
|
-
const
|
|
60
|
+
const listed = manager.environments.get();
|
|
61
|
+
/*
|
|
62
|
+
* A publication somebody else owns, which no listing of the viewer's holds
|
|
63
|
+
* (D-12, E2-19): read by its version uid, offered under *Library* beside
|
|
64
|
+
* the rest, priced by the rate the publication carries, and selected below,
|
|
65
|
+
* since it is what the reader came here to launch.
|
|
66
|
+
*/
|
|
67
|
+
const [libraryEntry, setLibraryEntry] = useState();
|
|
68
|
+
const [libraryRefusal, setLibraryRefusal] = useState('');
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!libraryVersionUid) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
let current = true;
|
|
74
|
+
getEnvironmentPublication(iamStore.getState().token ?? '', libraryVersionUid, {}, runtimesStore.getState().runtimesUrl)
|
|
75
|
+
.then(publication => {
|
|
76
|
+
if (!current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const entry = libraryEnvironmentOf(publication);
|
|
80
|
+
if (entry) {
|
|
81
|
+
setLibraryEntry(entry);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
setLibraryRefusal('This environment is no longer published, so it cannot be launched from the Library.');
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.catch(() => {
|
|
88
|
+
if (current) {
|
|
89
|
+
setLibraryRefusal('This published environment could not be read, so it cannot be launched from here.');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return () => {
|
|
93
|
+
current = false;
|
|
94
|
+
};
|
|
95
|
+
}, [libraryVersionUid]);
|
|
96
|
+
const environments = useMemo(() => libraryEntry && !listed.some(entry => entry.name === libraryEntry.name)
|
|
97
|
+
? [...listed, libraryEntry]
|
|
98
|
+
: listed, [listed, libraryEntry]);
|
|
60
99
|
/*
|
|
61
100
|
* The sandboxes of this Jupyter Server, offered beside those of the platform.
|
|
62
101
|
*
|
|
@@ -168,6 +207,17 @@ export function CodeSandboxLauncher(props) {
|
|
|
168
207
|
const [runtimeName, setRuntimeName] = useState(() => givenNameFor(firstLaunchableCodeSandboxEnvironment(environments)));
|
|
169
208
|
// Whether the runtim name has been changed by the user or not
|
|
170
209
|
const [hasCustomRuntimeName, setHasCustomRuntimeName] = useState(false);
|
|
210
|
+
// The publication the reader came to launch, selected once it is read.
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
if (!libraryEntry) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
setSelection(libraryEntry.name);
|
|
216
|
+
if (!hasCustomRuntimeName) {
|
|
217
|
+
setRuntimeName(givenNameFor(libraryEntry));
|
|
218
|
+
}
|
|
219
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
220
|
+
}, [libraryEntry]);
|
|
171
221
|
const [homeFolder, setHomeFolder] = useState(false);
|
|
172
222
|
const [openExample, setOpenExample] = useState(false);
|
|
173
223
|
const [waitingForRuntime, setWaitingForRuntime] = useState(false);
|
|
@@ -294,7 +344,6 @@ export function CodeSandboxLauncher(props) {
|
|
|
294
344
|
}
|
|
295
345
|
}
|
|
296
346
|
else if (shouldStartRuntime) {
|
|
297
|
-
success = false;
|
|
298
347
|
let availableTrial = 1;
|
|
299
348
|
let retryDelay = NOT_AVAILABLE_INIT_RETRY;
|
|
300
349
|
// Should return success status.
|
|
@@ -437,7 +486,7 @@ export function CodeSandboxLauncher(props) {
|
|
|
437
486
|
event.preventDefault();
|
|
438
487
|
handleSubmitRuntime();
|
|
439
488
|
}
|
|
440
|
-
}, children: [_jsxs(FormControl, { disabled: !!kernelSnapshot?.environment || environments.length === 0, children: [_jsx(FormControl.Label, { children: "Environment" }), _jsx(CodeSandboxEnvironmentSelect, { disabled: !!kernelSnapshot?.environment || environments.length === 0, options: environmentOptions, selectedKey: selection, onSelect: handleSelectionChange }), _jsx(FormControl.Caption, { children: _jsx(_Fragment, { children: markdownParser ? (_jsx(Box, { sx: { img: { maxWidth: '100%' } }, children: _jsx(Markdown, { text: description, markdownParser: markdownParser, sanitizer: sanitizer }) })) : sanitizer ? (
|
|
489
|
+
}, children: [libraryRefusal ? (_jsx(Text, { as: "p", sx: { color: 'attention.fg' }, "data-testid": "library-refusal", children: libraryRefusal })) : null, _jsxs(FormControl, { disabled: !!kernelSnapshot?.environment || environments.length === 0, children: [_jsx(FormControl.Label, { children: "Environment" }), _jsx(CodeSandboxEnvironmentSelect, { disabled: !!kernelSnapshot?.environment || environments.length === 0, options: environmentOptions, selectedKey: selection, onSelect: handleSelectionChange }), _jsx(FormControl.Caption, { children: _jsx(_Fragment, { children: markdownParser ? (_jsx(Box, { sx: { img: { maxWidth: '100%' } }, children: _jsx(Markdown, { text: description, markdownParser: markdownParser, sanitizer: sanitizer }) })) : sanitizer ? (
|
|
441
490
|
// The description of an environment carries markup. With no
|
|
442
491
|
// parser to render it, the sanitizer of the application is
|
|
443
492
|
// enough to show it as what it is rather than as its tags.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module components/code-sandboxes/codeSandboxEnvironments
|
|
16
16
|
*/
|
|
17
|
-
import type { IDatalayerEnvironment } from '../../models';
|
|
17
|
+
import type { IDatalayerEnvironment, IEnvironmentPublicationRecord } from '../../models';
|
|
18
18
|
import type { ICodeSandboxEnvironmentOption } from './CodeSandboxEnvironmentSelect';
|
|
19
19
|
/**
|
|
20
20
|
* The variant a sandbox of THIS platform starts on.
|
|
@@ -84,3 +84,14 @@ export declare function codeSandboxEnvironmentOption(environment: IDatalayerEnvi
|
|
|
84
84
|
export declare function codeSandboxEnvironmentOptions(environments: readonly IDatalayerEnvironment[], viewer?: ICodeSandboxEnvironmentViewer, decorate?: (environment: IDatalayerEnvironment) => Partial<ICodeSandboxEnvironmentOption>): ICodeSandboxEnvironmentOption[];
|
|
85
85
|
/** The first entry a launcher may open on: never one that cannot be launched. */
|
|
86
86
|
export declare function firstLaunchableCodeSandboxEnvironment(environments: readonly IDatalayerEnvironment[]): IDatalayerEnvironment | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* A publication, as the picker offers it to somebody who does not own it
|
|
89
|
+
* (D-12, E2-19).
|
|
90
|
+
*
|
|
91
|
+
* Nobody's listing holds another account's environment, so a reader who came
|
|
92
|
+
* from its Library page is offered it from the publication itself: launched by
|
|
93
|
+
* its environment uid and the version the publication froze, listed under
|
|
94
|
+
* *Library*, and priced by the rate the publication carries. `undefined` for a
|
|
95
|
+
* publication that has been withdrawn, which is no longer anyone's to launch.
|
|
96
|
+
*/
|
|
97
|
+
export declare function libraryEnvironmentOf(publication: IEnvironmentPublicationRecord): IDatalayerEnvironment | undefined;
|