@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.
Files changed (107) hide show
  1. package/README.md +1 -2
  2. package/lib/AgentNode.js +3 -3
  3. package/lib/api/runtimes/environments.d.ts +22 -1
  4. package/lib/api/runtimes/environments.js +31 -0
  5. package/lib/api/runtimes/healthz.js +4 -2
  6. package/lib/api/spacer/healthz.js +4 -2
  7. package/lib/chat/ChatFloating.d.ts +8 -1
  8. package/lib/chat/ChatFloating.js +5 -2
  9. package/lib/chat/base/ChatBase.js +39 -25
  10. package/lib/chat/base/modelChoice.d.ts +62 -0
  11. package/lib/chat/base/modelChoice.js +56 -0
  12. package/lib/chat/display/ButtonGlow.d.ts +9 -0
  13. package/lib/chat/display/ButtonGlow.js +60 -0
  14. package/lib/chat/display/EmptyState.js +4 -1
  15. package/lib/chat/header/ChatHeaderBase.js +4 -56
  16. package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
  17. package/lib/chat/header/ChatViewModeToggle.js +55 -0
  18. package/lib/chat/index.d.ts +1 -0
  19. package/lib/chat/index.js +1 -0
  20. package/lib/chat/messages/ChatMessageList.js +13 -5
  21. package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
  22. package/lib/chat/messages/FormSubmissionMessage.js +49 -0
  23. package/lib/chat/messages/formSubmission.d.ts +41 -0
  24. package/lib/chat/messages/formSubmission.js +90 -0
  25. package/lib/chat/prompt/menus/ModelSelector.js +11 -4
  26. package/lib/chat/styles/streamdownStyles.js +40 -0
  27. package/lib/client/mixins/RuntimesMixin.js +1 -1
  28. package/lib/client/mixins/SpacerMixin.js +3 -3
  29. package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
  30. package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
  31. package/lib/components/a2ui/index.d.ts +1 -0
  32. package/lib/components/a2ui/index.js +1 -0
  33. package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
  34. package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
  35. package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
  36. package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
  37. package/lib/components/icons/ArtifactIcon.js +2 -1
  38. package/lib/examples/A2UiAgentExample.js +36 -223
  39. package/lib/examples/A2UiJupyterOutputExample.js +11 -1
  40. package/lib/examples/A2UiViewerExample.d.ts +9 -2
  41. package/lib/examples/A2UiViewerExample.js +33 -3
  42. package/lib/examples/DecksAgent.d.ts +1 -1
  43. package/lib/examples/DecksAgent.js +1 -1
  44. package/lib/examples/main.js +37 -35
  45. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
  46. package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
  47. package/lib/loop/core/index.d.ts +44 -4
  48. package/lib/loop/core/index.js +29 -2
  49. package/lib/loop/embed/LoopEmbed.js +7 -0
  50. package/lib/loop/index.d.ts +1 -1
  51. package/lib/loop/index.js +4 -1
  52. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
  53. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
  54. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
  55. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
  56. package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
  57. package/lib/loop/plugins/a2ui-surface/index.js +91 -0
  58. package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
  59. package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
  60. package/lib/loop/plugins/chat/ChatView.js +95 -15
  61. package/lib/loop/plugins/chat/index.d.ts +14 -0
  62. package/lib/loop/plugins/chat/index.js +1 -0
  63. package/lib/loop/plugins/document/index.js +4 -0
  64. package/lib/loop/plugins/notebook/index.js +4 -0
  65. package/lib/loop/plugins/page-layout/index.d.ts +36 -0
  66. package/lib/loop/plugins/page-layout/index.js +87 -7
  67. package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
  68. package/lib/loop/presets.d.ts +19 -0
  69. package/lib/loop/presets.js +9 -1
  70. package/lib/models/Deck.d.ts +14 -0
  71. package/lib/models/Deck.js +5 -0
  72. package/lib/models/Environment.d.ts +117 -0
  73. package/lib/models/ItemType.d.ts +1 -1
  74. package/lib/models/Runtime.d.ts +6 -0
  75. package/lib/models/SpaceItem.d.ts +2 -1
  76. package/lib/models/environments.generated.d.ts +35 -1
  77. package/lib/models/environments.generated.js +1 -1
  78. package/lib/models/index.d.ts +1 -0
  79. package/lib/models/index.js +1 -0
  80. package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
  81. package/lib/protocols/BrowserAgentAdapter.js +11 -4
  82. package/lib/runtimes/actions.d.ts +5 -0
  83. package/lib/runtimes/actions.js +19 -0
  84. package/lib/specs/agents/agents.d.ts +3 -1
  85. package/lib/specs/agents/agents.js +426 -130
  86. package/lib/specs/frontendTools.d.ts +1 -0
  87. package/lib/specs/frontendTools.js +31 -0
  88. package/lib/specs/mcpServers.js +13 -13
  89. package/lib/specs/models.js +7 -7
  90. package/lib/specs/outputs.js +9 -0
  91. package/lib/specs/skills.js +7 -7
  92. package/lib/specs/teams/teams.js +1 -1
  93. package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
  94. package/lib/types/outputs.d.ts +2 -0
  95. package/package.json +10 -4
  96. package/patches/@primer+react+37.31.0.patch +390 -0
  97. package/patches/eslint-plugin-react+7.37.5.patch +65 -0
  98. package/scripts/apply-patches.sh +15 -1
  99. package/scripts/codegen/compose.py +4 -4
  100. package/scripts/codegen/generate_agents.py +1 -2
  101. package/scripts/codegen/generate_frontend_tools.py +1 -1
  102. package/scripts/codegen/generate_mcp_servers.py +2 -2
  103. package/scripts/codegen/generate_models.py +33 -8
  104. package/scripts/codegen/generate_outputs.py +2 -0
  105. package/scripts/codegen/generate_skills.py +2 -2
  106. package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
  107. 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
+ }
@@ -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 ? (_jsx(Text, { sx: {
701
- fontSize: 1,
702
- whiteSpace: 'pre-wrap',
703
- wordBreak: 'break-word',
704
- }, children: getMessageText(message) })) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: normalizeAssistantMarkdown(getMessageText(message) || (isStreaming ? '...' : '')) }) }))] }), renderMessageFooter ? (_jsx(Box, { sx: {
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 = 'unknown';
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 = 'unknown';
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;
@@ -1,2 +1,3 @@
1
+ export * from './A2uiMarkdownProvider';
1
2
  export * from './A2uiSurfaceComposed';
2
3
  export * from './styles';
@@ -2,5 +2,6 @@
2
2
  * Copyright (c) 2025-2026 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ export * from './A2uiMarkdownProvider';
5
6
  export * from './A2uiSurfaceComposed';
6
7
  export * from './styles';
@@ -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 environments = manager.environments.get();
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;