@datalayer/agent-runtimes 1.3.75 → 1.3.79
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/lib/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +48 -11
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantStage.d.ts +51 -5
- package/lib/chat/assistant/AssistantStage.js +60 -10
- package/lib/chat/assistant/BalloonParts.d.ts +18 -4
- package/lib/chat/assistant/BalloonParts.js +46 -41
- package/lib/chat/assistant/BalloonVisual.js +2 -1
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +8 -6
- package/lib/chat/assistant/SpeechBalloon.js +29 -30
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +20 -0
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +24 -18
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- package/lib/components/teams/A2ATeamGraph.d.ts +30 -3
- package/lib/components/teams/A2ATeamGraph.js +264 -41
- package/lib/components/teams/TeamNotebookView.js +15 -0
- package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +10 -6
- package/lib/components/teams/useA2ATeam.js +29 -8
- package/lib/examples/AgentA2ATeamExample.js +6 -0
- package/lib/examples/AssistantExample.js +3 -24
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +21 -3
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +53 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +5 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the assistant's menu knows of its agent and of its code sandbox, for
|
|
3
|
+
* *Agent Details…* and *Code Sandbox Details…*.
|
|
4
|
+
*
|
|
5
|
+
* Light on purpose: types and pure helpers only. The dialogs that draw them
|
|
6
|
+
* — `AgentDetails`, jupyter-react's `KernelVariables` — are loaded when one
|
|
7
|
+
* is opened, never with the character.
|
|
8
|
+
*
|
|
9
|
+
* @module chat/assistant/assistantDetails
|
|
10
|
+
*/
|
|
11
|
+
import type { Kernel } from '@jupyterlab/services';
|
|
12
|
+
import type { KernelVariablesExecution } from '@datalayer/jupyter-react';
|
|
13
|
+
/** Where a sandbox runs. */
|
|
14
|
+
export type AssistantSandboxKind =
|
|
15
|
+
/** Pyodide, in this page. */
|
|
16
|
+
'browser'
|
|
17
|
+
/** A Jupyter server on this machine, the agent's own. */
|
|
18
|
+
| 'local'
|
|
19
|
+
/** A Jupyter server elsewhere. */
|
|
20
|
+
| 'jupyter'
|
|
21
|
+
/** A Datalayer cloud runtime. */
|
|
22
|
+
| 'cloud'
|
|
23
|
+
/** In the agent runtime's process, or at a provider (`eval`, `e2b`, …). */
|
|
24
|
+
| 'runtime';
|
|
25
|
+
/** A code sandbox, as much of it as its host knows. */
|
|
26
|
+
export type AssistantSandbox = {
|
|
27
|
+
kind: AssistantSandboxKind;
|
|
28
|
+
/** `running`, `starting`, `idle`, `stopped`, `error`. */
|
|
29
|
+
status?: string;
|
|
30
|
+
/** The sandbox manager's variant: `jupyter-server`, `eval`, `datalayer`, a provider. */
|
|
31
|
+
variant?: string;
|
|
32
|
+
/** The Jupyter server, or the base the sandbox is reached at. Shown without its tokens. */
|
|
33
|
+
url?: string;
|
|
34
|
+
/** The token the Jupyter server wants: used to connect, never shown. */
|
|
35
|
+
token?: string;
|
|
36
|
+
/** The kernel the sandbox runs, on that server. */
|
|
37
|
+
kernelId?: string;
|
|
38
|
+
/** The agent whose sandbox it is (a local server's per-agent sandbox). */
|
|
39
|
+
agentId?: string;
|
|
40
|
+
/** The environment it runs in. */
|
|
41
|
+
environment?: string;
|
|
42
|
+
/** A cloud runtime's identity: its uid, never its pod's name. */
|
|
43
|
+
runtimeUid?: string;
|
|
44
|
+
/** When a cloud runtime ends; `null` when it is not metered. */
|
|
45
|
+
expiresAt?: string | number | null;
|
|
46
|
+
/** What a cloud runtime burns, in credits per hour. */
|
|
47
|
+
burningRate?: number;
|
|
48
|
+
startedAt?: string | number;
|
|
49
|
+
lastActivity?: string | number;
|
|
50
|
+
/** The kernel, when the page talks to it: listed live, widgets drawn. */
|
|
51
|
+
connection?: Kernel.IKernelConnection | null;
|
|
52
|
+
/**
|
|
53
|
+
* The agent-runtimes server whose `/api/v1/sandbox/execute` runs code in
|
|
54
|
+
* the agent's sandbox, when the page has no kernel connection to it.
|
|
55
|
+
*/
|
|
56
|
+
serverUrl?: string;
|
|
57
|
+
};
|
|
58
|
+
/** Whether a sandbox is up: one with no status is taken to be. */
|
|
59
|
+
export declare function sandboxIsLive(sandbox: AssistantSandbox): boolean;
|
|
60
|
+
/** What the dialog says of a sandbox that is not running yet. */
|
|
61
|
+
export declare const NO_SANDBOX_YET = "No sandbox is running yet: run a cell or ask the agent to execute code.";
|
|
62
|
+
/** What each kind is called. */
|
|
63
|
+
export declare const SANDBOX_KIND_LABELS: Record<AssistantSandboxKind, string>;
|
|
64
|
+
/** Whether a URL is on this machine. */
|
|
65
|
+
export declare function isLocalUrl(url: string | undefined): boolean;
|
|
66
|
+
/** A URL fit to show: no credentials, no token in its query. */
|
|
67
|
+
export declare function displayUrl(url: string | undefined): string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* A sandbox from the status its server reports — the Loop's sandbox
|
|
70
|
+
* service, or the agent's codemode status — and what the host adds.
|
|
71
|
+
*/
|
|
72
|
+
export declare function assistantSandboxOf(status: Record<string, unknown> | null | undefined, options?: {
|
|
73
|
+
agentId?: string;
|
|
74
|
+
serverUrl?: string;
|
|
75
|
+
state?: string;
|
|
76
|
+
}): AssistantSandbox | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Run code in an agent's sandbox through its agent-runtimes server
|
|
79
|
+
* (`POST /api/v1/sandbox/execute`), whatever the variant: what a page with
|
|
80
|
+
* no kernel connection to the sandbox lists its variables with.
|
|
81
|
+
*/
|
|
82
|
+
export declare function sandboxExecuteOverHttp(serverUrl: string, agentId?: string): (code: string) => Promise<KernelVariablesExecution>;
|
|
83
|
+
/** How long until a moment, for a reader: `1 h 20 min`, `never`. */
|
|
84
|
+
export declare function timeLeft(expiresAt: string | number | null | undefined, now?: number): string | undefined;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** The statuses of a sandbox that has something to read. */
|
|
6
|
+
const LIVE = new Set(['running', 'idle', 'busy', 'starting']);
|
|
7
|
+
/** Whether a sandbox is up: one with no status is taken to be. */
|
|
8
|
+
export function sandboxIsLive(sandbox) {
|
|
9
|
+
return sandbox.status === undefined || LIVE.has(sandbox.status);
|
|
10
|
+
}
|
|
11
|
+
/** What the dialog says of a sandbox that is not running yet. */
|
|
12
|
+
export const NO_SANDBOX_YET = 'No sandbox is running yet: run a cell or ask the agent to execute code.';
|
|
13
|
+
/** What each kind is called. */
|
|
14
|
+
export const SANDBOX_KIND_LABELS = {
|
|
15
|
+
browser: 'Python in your browser (Pyodide)',
|
|
16
|
+
local: 'Local Jupyter server',
|
|
17
|
+
jupyter: 'Jupyter server',
|
|
18
|
+
cloud: 'Datalayer cloud runtime',
|
|
19
|
+
runtime: 'In the agent runtime',
|
|
20
|
+
};
|
|
21
|
+
/** Whether a URL is on this machine. */
|
|
22
|
+
export function isLocalUrl(url) {
|
|
23
|
+
if (!url) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const host = new URL(url).hostname;
|
|
28
|
+
return (host === 'localhost' ||
|
|
29
|
+
host === '127.0.0.1' ||
|
|
30
|
+
host === '::1' ||
|
|
31
|
+
host === '[::1]' ||
|
|
32
|
+
host.endsWith('.localhost'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Query parameters that carry a secret. */
|
|
39
|
+
const SECRET_PARAMS = /^(token|access_token|api_key|apikey|key|auth|password|secret|signature|sig|jwt)$/i;
|
|
40
|
+
/** A URL fit to show: no credentials, no token in its query. */
|
|
41
|
+
export function displayUrl(url) {
|
|
42
|
+
if (!url) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const parsed = new URL(url);
|
|
47
|
+
parsed.username = '';
|
|
48
|
+
parsed.password = '';
|
|
49
|
+
for (const name of [...parsed.searchParams.keys()]) {
|
|
50
|
+
if (SECRET_PARAMS.test(name)) {
|
|
51
|
+
parsed.searchParams.delete(name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return parsed.toString();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Not a URL: shown up to its query, which is where a token would be.
|
|
58
|
+
return url.split('?')[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const text = (value) => typeof value === 'string' && value ? value : undefined;
|
|
62
|
+
/**
|
|
63
|
+
* A sandbox from the status its server reports — the Loop's sandbox
|
|
64
|
+
* service, or the agent's codemode status — and what the host adds.
|
|
65
|
+
*/
|
|
66
|
+
export function assistantSandboxOf(status, options = {}) {
|
|
67
|
+
if (!status) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const variant = text(status.variant);
|
|
71
|
+
const url = text(status.jupyter_url) ?? text(status.agent_base_url);
|
|
72
|
+
const running = status.sandbox_running === true || status.jupyter_connected === true;
|
|
73
|
+
if (!variant && !running) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const kind = variant === 'datalayer'
|
|
77
|
+
? 'cloud'
|
|
78
|
+
: variant === 'jupyter-server' || variant === 'jupyter'
|
|
79
|
+
? isLocalUrl(text(status.jupyter_url))
|
|
80
|
+
? 'local'
|
|
81
|
+
: 'jupyter'
|
|
82
|
+
: 'runtime';
|
|
83
|
+
return {
|
|
84
|
+
kind,
|
|
85
|
+
status: options.state ?? (running ? 'running' : 'stopped'),
|
|
86
|
+
variant,
|
|
87
|
+
url,
|
|
88
|
+
token: text(status.jupyter_token),
|
|
89
|
+
kernelId: text(status.kernel_id),
|
|
90
|
+
agentId: options.agentId,
|
|
91
|
+
environment: text(status.environment_name),
|
|
92
|
+
serverUrl: options.serverUrl,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run code in an agent's sandbox through its agent-runtimes server
|
|
97
|
+
* (`POST /api/v1/sandbox/execute`), whatever the variant: what a page with
|
|
98
|
+
* no kernel connection to the sandbox lists its variables with.
|
|
99
|
+
*/
|
|
100
|
+
export function sandboxExecuteOverHttp(serverUrl, agentId) {
|
|
101
|
+
const base = serverUrl.replace(/\/+$/, '').replace(/\/api\/v1$/, '');
|
|
102
|
+
return async (code) => {
|
|
103
|
+
const response = await fetch(`${base}/api/v1/sandbox/execute`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'content-type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({ code, agent_id: agentId }),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
return {
|
|
110
|
+
stdout: '',
|
|
111
|
+
error: `The sandbox did not run the code (HTTP ${response.status}).`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const payload = (await response.json());
|
|
115
|
+
return { stdout: payload.stdout ?? '', error: payload.error ?? undefined };
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** How long until a moment, for a reader: `1 h 20 min`, `never`. */
|
|
119
|
+
export function timeLeft(expiresAt, now = Date.now()) {
|
|
120
|
+
if (expiresAt === null) {
|
|
121
|
+
return 'never';
|
|
122
|
+
}
|
|
123
|
+
if (expiresAt === undefined || expiresAt === '') {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const at = typeof expiresAt === 'number'
|
|
127
|
+
? expiresAt < 1e12
|
|
128
|
+
? expiresAt * 1000
|
|
129
|
+
: expiresAt
|
|
130
|
+
: Date.parse(expiresAt);
|
|
131
|
+
if (!Number.isFinite(at)) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const minutes = Math.round((at - now) / 60000);
|
|
135
|
+
if (minutes <= 0) {
|
|
136
|
+
return 'ended';
|
|
137
|
+
}
|
|
138
|
+
const hours = Math.floor(minutes / 60);
|
|
139
|
+
return hours ? `${hours} h ${minutes % 60} min` : `${minutes} min`;
|
|
140
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type AssistantCharacterData } from './types';
|
|
2
|
+
/** What a file picker for a character accepts. */
|
|
3
|
+
export declare const CHARACTER_FILES_ACCEPT = ".acs,.js,.png,.gif,.webp,.jpg,.jpeg";
|
|
4
|
+
/** The sentence for a pick that is not a character. */
|
|
5
|
+
export declare const CHARACTER_FILES_EXPECTED = "Pick one .acs file, or a clippy.js character: its agent.js and its map image (and a sounds file if you have one).";
|
|
6
|
+
/** A picked file, as much of `File` as the reader needs. */
|
|
7
|
+
export type PickedFile = Blob & {
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
/** Which of the picked files make the character, or why none do. */
|
|
11
|
+
export declare function characterFilesOf(files: ReadonlyArray<PickedFile>): {
|
|
12
|
+
kind: 'acs';
|
|
13
|
+
acs: PickedFile;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'clippy';
|
|
16
|
+
agentJs: PickedFile;
|
|
17
|
+
map: PickedFile;
|
|
18
|
+
sounds?: PickedFile;
|
|
19
|
+
} | {
|
|
20
|
+
problem: string;
|
|
21
|
+
};
|
|
22
|
+
/** Read the picked files as a character; refuses in a sentence. */
|
|
23
|
+
export declare function readCharacterFiles(files: ReadonlyArray<PickedFile>): Promise<AssistantCharacterData>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The files a person picked, read as one character (LOOP T-26): one
|
|
7
|
+
* Microsoft Agent `.acs`, or a clippy.js character's `agent.js` and map
|
|
8
|
+
* image (and its sounds file, if there is one). Read in the page; nothing is
|
|
9
|
+
* sent anywhere.
|
|
10
|
+
*
|
|
11
|
+
* @module chat/assistant/formats/files
|
|
12
|
+
*/
|
|
13
|
+
import { readAcsCharacter } from './acs';
|
|
14
|
+
import { readClippyCharacter } from './clippy';
|
|
15
|
+
import { AssistantCharacterFormatError, } from './types';
|
|
16
|
+
/** What a file picker for a character accepts. */
|
|
17
|
+
export const CHARACTER_FILES_ACCEPT = '.acs,.js,.png,.gif,.webp,.jpg,.jpeg';
|
|
18
|
+
/** The sentence for a pick that is not a character. */
|
|
19
|
+
export const CHARACTER_FILES_EXPECTED = 'Pick one .acs file, or a clippy.js character: its agent.js and its map image (and a sounds file if you have one).';
|
|
20
|
+
/** Which of the picked files make the character, or why none do. */
|
|
21
|
+
export function characterFilesOf(files) {
|
|
22
|
+
const acs = files.find(file => file.name.toLowerCase().endsWith('.acs'));
|
|
23
|
+
if (acs) {
|
|
24
|
+
return { kind: 'acs', acs };
|
|
25
|
+
}
|
|
26
|
+
const agentJs = files.find(file => file.name.toLowerCase() === 'agent.js');
|
|
27
|
+
const map = files.find(file => /\.(png|gif|webp|jpe?g)$/i.test(file.name));
|
|
28
|
+
const sounds = files.find(file => /^sounds-.*\.js$/i.test(file.name));
|
|
29
|
+
if (!agentJs || !map) {
|
|
30
|
+
return { problem: CHARACTER_FILES_EXPECTED };
|
|
31
|
+
}
|
|
32
|
+
return { kind: 'clippy', agentJs, map, ...(sounds ? { sounds } : {}) };
|
|
33
|
+
}
|
|
34
|
+
/** Read the picked files as a character; refuses in a sentence. */
|
|
35
|
+
export async function readCharacterFiles(files) {
|
|
36
|
+
const picked = characterFilesOf(files);
|
|
37
|
+
if ('problem' in picked) {
|
|
38
|
+
throw new AssistantCharacterFormatError(picked.problem);
|
|
39
|
+
}
|
|
40
|
+
if (picked.kind === 'acs') {
|
|
41
|
+
return readAcsCharacter(await picked.acs.arrayBuffer());
|
|
42
|
+
}
|
|
43
|
+
return readClippyCharacter({
|
|
44
|
+
agentJs: await picked.agentJs.text(),
|
|
45
|
+
mapPng: picked.map,
|
|
46
|
+
soundsJs: picked.sounds ? await picked.sounds.text() : undefined,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -11,3 +11,5 @@ export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcs
|
|
|
11
11
|
export type { AcsFile, AcsAnimation, AcsFrame, AcsImage } from './acs';
|
|
12
12
|
export { decompressAgentData } from './agentCompression';
|
|
13
13
|
export { stateAnimations } from './stateAnimations';
|
|
14
|
+
export { CHARACTER_FILES_ACCEPT, CHARACTER_FILES_EXPECTED, characterFilesOf, readCharacterFiles, } from './files';
|
|
15
|
+
export type { PickedFile } from './files';
|
|
@@ -13,3 +13,4 @@ export { readClippyCharacter, readClippyAgent, readClippySounds, } from './clipp
|
|
|
13
13
|
export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcsCell, } from './acs';
|
|
14
14
|
export { decompressAgentData } from './agentCompression';
|
|
15
15
|
export { stateAnimations } from './stateAnimations';
|
|
16
|
+
export { CHARACTER_FILES_ACCEPT, CHARACTER_FILES_EXPECTED, characterFilesOf, readCharacterFiles, } from './files';
|
|
@@ -76,6 +76,11 @@ export interface AssistantSaying {
|
|
|
76
76
|
text: string;
|
|
77
77
|
/** Whether there was more than the balloon holds. */
|
|
78
78
|
more: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Its words as written, markdown and all: what the `current` balloon
|
|
81
|
+
* draws, through the chat's markdown.
|
|
82
|
+
*/
|
|
83
|
+
markdown: string;
|
|
79
84
|
}
|
|
80
85
|
/** How much of a message is kept as the agent's saying before it is cut. */
|
|
81
86
|
export declare const SAYING_LIMIT = 220;
|
|
@@ -84,20 +89,8 @@ export declare const SAYING_LIMIT = 220;
|
|
|
84
89
|
* code blocks left out, links and Markdown read as plain words.
|
|
85
90
|
*/
|
|
86
91
|
export declare function plainWordsOf(content: unknown): string;
|
|
87
|
-
/** A message
|
|
88
|
-
export
|
|
89
|
-
id: string;
|
|
90
|
-
/** Who said it: the person, or the agent. */
|
|
91
|
-
role: 'user' | 'assistant';
|
|
92
|
-
/** Its words, plain. */
|
|
93
|
-
text: string;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The conversation as the `history` balloon lists it (T-23): the person's
|
|
97
|
-
* and the agent's messages with words, in order — not the tool calls, not
|
|
98
|
-
* an empty message — the same messages its header counts.
|
|
99
|
-
*/
|
|
100
|
-
export declare function balloonHistoryOf(items: readonly unknown[]): BalloonHistoryMessage[];
|
|
92
|
+
/** A message's text parts as written, markdown and all. */
|
|
93
|
+
export declare function markdownOf(content: unknown): string;
|
|
101
94
|
/**
|
|
102
95
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
103
96
|
* balloon, while the conversation is closed (T-23). Only its own messages
|
|
@@ -75,25 +75,19 @@ export function plainWordsOf(content) {
|
|
|
75
75
|
.replace(/\s+/g, ' ')
|
|
76
76
|
.trim();
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const text = plainWordsOf(item.content);
|
|
92
|
-
if (text) {
|
|
93
|
-
history.push({ id: String(item.id ?? index), role: item.role, text });
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
return history;
|
|
78
|
+
/** A message's text parts as written, markdown and all. */
|
|
79
|
+
export function markdownOf(content) {
|
|
80
|
+
return typeof content === 'string'
|
|
81
|
+
? content
|
|
82
|
+
: Array.isArray(content)
|
|
83
|
+
? content
|
|
84
|
+
.map(part => part &&
|
|
85
|
+
typeof part === 'object' &&
|
|
86
|
+
part.type === 'text'
|
|
87
|
+
? String(part.text ?? '')
|
|
88
|
+
: '')
|
|
89
|
+
.join('\n\n')
|
|
90
|
+
: '';
|
|
97
91
|
}
|
|
98
92
|
/**
|
|
99
93
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
@@ -111,8 +105,14 @@ export function latestSaying(items) {
|
|
|
111
105
|
if (!plain) {
|
|
112
106
|
continue;
|
|
113
107
|
}
|
|
108
|
+
const markdown = markdownOf(item.content);
|
|
114
109
|
if (plain.length <= SAYING_LIMIT) {
|
|
115
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
id: String(item.id ?? index),
|
|
112
|
+
text: plain,
|
|
113
|
+
more: false,
|
|
114
|
+
markdown,
|
|
115
|
+
};
|
|
116
116
|
}
|
|
117
117
|
const cut = plain.slice(0, SAYING_LIMIT);
|
|
118
118
|
const atWord = cut.slice(0, Math.max(cut.lastIndexOf(' '), SAYING_LIMIT * 0.6));
|
|
@@ -120,6 +120,7 @@ export function latestSaying(items) {
|
|
|
120
120
|
id: String(item.id ?? index),
|
|
121
121
|
text: `${atWord.trimEnd()}…`,
|
|
122
122
|
more: true,
|
|
123
|
+
markdown,
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
return undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DisplayItem, ToolCallMessage } from '../../types/chat';
|
|
1
2
|
/**
|
|
2
3
|
* How the balloon shows the conversation: `history`, every message,
|
|
3
4
|
* scrolled, the composer last; `current`, only what it says or does now, in
|
|
@@ -67,3 +68,18 @@ export declare function toolAnnouncement(line: BalloonToolLine | undefined, prev
|
|
|
67
68
|
* header counts them: the person's and the agent's, not the tool calls.
|
|
68
69
|
*/
|
|
69
70
|
export declare function conversationCount(items: readonly unknown[]): number;
|
|
71
|
+
/**
|
|
72
|
+
* A tool line as the chat holds it: a tool call (`ToolCallMessage`), drawn
|
|
73
|
+
* by the chat's own tool card in the balloon, its runtime's own words
|
|
74
|
+
* (`Asking Accounting…`) as its summary; or, when it names no tool — what
|
|
75
|
+
* the agent is doing, in its own words only — the agent's message saying
|
|
76
|
+
* so.
|
|
77
|
+
*/
|
|
78
|
+
export declare function displayItemOfLine(line: BalloonToolLine): DisplayItem;
|
|
79
|
+
/** A tool line as the chat's own tool call (`ToolCallMessage`). */
|
|
80
|
+
export declare function toolCallOfLine(line: BalloonToolLine): ToolCallMessage;
|
|
81
|
+
/**
|
|
82
|
+
* The conversation with the tool being called, when the conversation does
|
|
83
|
+
* not hold that call already: what the history balloon lists.
|
|
84
|
+
*/
|
|
85
|
+
export declare function withToolCall(items: readonly DisplayItem[], line: BalloonToolLine | undefined): readonly DisplayItem[];
|
|
@@ -116,3 +116,49 @@ export function conversationCount(items) {
|
|
|
116
116
|
typeof message.toolName !== 'string');
|
|
117
117
|
}).length;
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* A tool line as the chat holds it: a tool call (`ToolCallMessage`), drawn
|
|
121
|
+
* by the chat's own tool card in the balloon, its runtime's own words
|
|
122
|
+
* (`Asking Accounting…`) as its summary; or, when it names no tool — what
|
|
123
|
+
* the agent is doing, in its own words only — the agent's message saying
|
|
124
|
+
* so.
|
|
125
|
+
*/
|
|
126
|
+
export function displayItemOfLine(line) {
|
|
127
|
+
if (!line.tool && !line.name) {
|
|
128
|
+
return {
|
|
129
|
+
id: `balloon-activity:${line.id}`,
|
|
130
|
+
role: 'assistant',
|
|
131
|
+
content: line.words ?? '',
|
|
132
|
+
createdAt: new Date(0),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return toolCallOfLine(line);
|
|
136
|
+
}
|
|
137
|
+
/** A tool line as the chat's own tool call (`ToolCallMessage`). */
|
|
138
|
+
export function toolCallOfLine(line) {
|
|
139
|
+
return {
|
|
140
|
+
id: `balloon-tool:${line.id}`,
|
|
141
|
+
type: 'tool-call',
|
|
142
|
+
toolCallId: line.id,
|
|
143
|
+
toolName: line.tool || line.name,
|
|
144
|
+
args: {},
|
|
145
|
+
status: line.phase === 'failed'
|
|
146
|
+
? 'error'
|
|
147
|
+
: line.phase === 'done'
|
|
148
|
+
? 'complete'
|
|
149
|
+
: 'executing',
|
|
150
|
+
...(line.words ? { summary: line.words } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The conversation with the tool being called, when the conversation does
|
|
155
|
+
* not hold that call already: what the history balloon lists.
|
|
156
|
+
*/
|
|
157
|
+
export function withToolCall(items, line) {
|
|
158
|
+
if (!line ||
|
|
159
|
+
items.some(item => item.toolCallId === line.id ||
|
|
160
|
+
item.id === line.id)) {
|
|
161
|
+
return items;
|
|
162
|
+
}
|
|
163
|
+
return [...items, displayItemOfLine(line)];
|
|
164
|
+
}
|
|
@@ -60,6 +60,7 @@ const EphemeralDocument = React.lazy(() => import('../document/EphemeralDocument
|
|
|
60
60
|
import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHooks';
|
|
61
61
|
import { useAgentInspectorSink } from '../../components/inspector/agentInspector';
|
|
62
62
|
import { chatInspectorRecorder, } from '../../components/inspector/chatInspect';
|
|
63
|
+
import { loopMessageChange, speakerOf, withLoopMessage } from './loopMessage';
|
|
63
64
|
// Tracks pending prompts already auto-sent for a given conversation scope.
|
|
64
65
|
// This prevents layout-driven unmount/remount cycles from re-sending prompts.
|
|
65
66
|
const sentPendingPromptKeys = new Set();
|
|
@@ -2682,6 +2683,25 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2682
2683
|
}
|
|
2683
2684
|
}
|
|
2684
2685
|
break;
|
|
2686
|
+
case 'activity': {
|
|
2687
|
+
// An application's code changed a message it sent: its author
|
|
2688
|
+
// said, or the message taken away (LOOP P-15).
|
|
2689
|
+
const change = loopMessageChange(event.activity);
|
|
2690
|
+
if (!change)
|
|
2691
|
+
break;
|
|
2692
|
+
setDisplayItems(prev => withLoopMessage(prev, change));
|
|
2693
|
+
if (useStoreMode) {
|
|
2694
|
+
if ('removed' in change) {
|
|
2695
|
+
useChatStore.getState().deleteMessage(change.id);
|
|
2696
|
+
}
|
|
2697
|
+
else {
|
|
2698
|
+
useChatStore.getState().updateMessage(change.id, {
|
|
2699
|
+
speaker: speakerOf(change.author),
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
break;
|
|
2704
|
+
}
|
|
2685
2705
|
case 'done':
|
|
2686
2706
|
/*
|
|
2687
2707
|
* What the turn cost, when nobody else is counting.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A message of an application's code that changed after it was sent
|
|
3
|
+
* (LOOP P-15).
|
|
4
|
+
*
|
|
5
|
+
* The session API says a message's author, or that it was taken away, with a
|
|
6
|
+
* `CUSTOM` event named `loop.message` — `{id, author}` or `{id, removed: true}`
|
|
7
|
+
* — which the AG-UI adapter hands on as an `activity`. A message whose text
|
|
8
|
+
* changed needs nothing here: it is written again under its id, and the chat
|
|
9
|
+
* draws it in place.
|
|
10
|
+
*/
|
|
11
|
+
import type { MessageSpeaker } from '../../types/messages';
|
|
12
|
+
/** The `CUSTOM` event's name. */
|
|
13
|
+
export declare const LOOP_MESSAGE = "loop.message";
|
|
14
|
+
/** What changed about a message already in the conversation. */
|
|
15
|
+
export type LoopMessageChange = {
|
|
16
|
+
id: string;
|
|
17
|
+
removed: true;
|
|
18
|
+
} | {
|
|
19
|
+
id: string;
|
|
20
|
+
author: string;
|
|
21
|
+
};
|
|
22
|
+
/** The change an activity says, or `null` when it says none. */
|
|
23
|
+
export declare function loopMessageChange(activity: {
|
|
24
|
+
type: string;
|
|
25
|
+
data: unknown;
|
|
26
|
+
} | undefined): LoopMessageChange | null;
|
|
27
|
+
/** Who a message is by, as the chat draws a named voice. */
|
|
28
|
+
export declare function speakerOf(author: string): MessageSpeaker;
|
|
29
|
+
/**
|
|
30
|
+
* The conversation with the change made: the message taken away, or given its
|
|
31
|
+
* author. Items that are not that message — tool calls among them — are left
|
|
32
|
+
* as they are.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withLoopMessage<T extends {
|
|
35
|
+
id?: string;
|
|
36
|
+
speaker?: MessageSpeaker;
|
|
37
|
+
}>(items: T[], change: LoopMessageChange): T[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** The `CUSTOM` event's name. */
|
|
6
|
+
export const LOOP_MESSAGE = 'loop.message';
|
|
7
|
+
/** The change an activity says, or `null` when it says none. */
|
|
8
|
+
export function loopMessageChange(activity) {
|
|
9
|
+
if (!activity || activity.type !== LOOP_MESSAGE)
|
|
10
|
+
return null;
|
|
11
|
+
const data = activity.data;
|
|
12
|
+
if (!data || typeof data.id !== 'string' || !data.id)
|
|
13
|
+
return null;
|
|
14
|
+
if (data.removed === true)
|
|
15
|
+
return { id: data.id, removed: true };
|
|
16
|
+
if (typeof data.author === 'string' && data.author.trim()) {
|
|
17
|
+
return { id: data.id, author: data.author.trim() };
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
/** Who a message is by, as the chat draws a named voice. */
|
|
22
|
+
export function speakerOf(author) {
|
|
23
|
+
return { id: `author:${author}`, name: author };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The conversation with the change made: the message taken away, or given its
|
|
27
|
+
* author. Items that are not that message — tool calls among them — are left
|
|
28
|
+
* as they are.
|
|
29
|
+
*/
|
|
30
|
+
export function withLoopMessage(items, change) {
|
|
31
|
+
if ('removed' in change) {
|
|
32
|
+
return items.filter(item => item.id !== change.id);
|
|
33
|
+
}
|
|
34
|
+
return items.map(item => item.id === change.id
|
|
35
|
+
? { ...item, speaker: speakerOf(change.author) }
|
|
36
|
+
: item);
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An agent's words, as markdown: what the chat's messages draw them with
|
|
3
|
+
* (`ChatMessageList`), and what the floating assistant's balloon draws its
|
|
4
|
+
* current line with — one renderer, so a code block, a table or a list
|
|
5
|
+
* looks the same wherever the words are read.
|
|
6
|
+
*
|
|
7
|
+
* @module chat/messages/ChatMarkdown
|
|
8
|
+
*/
|
|
9
|
+
import type { JSX } from 'react';
|
|
10
|
+
/** How dense the chat's components draw: the chat's own, or the balloon's. */
|
|
11
|
+
export type ChatDensity = 'comfortable' | 'compact';
|
|
12
|
+
export declare function ChatMarkdown({ text, density, }: {
|
|
13
|
+
text: string;
|
|
14
|
+
density?: ChatDensity;
|
|
15
|
+
}): JSX.Element;
|
|
16
|
+
export default ChatMarkdown;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from '@datalayer/primer-addons';
|
|
3
|
+
import { Streamdown } from 'streamdown';
|
|
4
|
+
import { streamdownMarkdownStyles } from '../styles/streamdownStyles';
|
|
5
|
+
import { normalizeAssistantMarkdown } from './assistantMarkdown';
|
|
6
|
+
export function ChatMarkdown({ text, density = 'comfortable', }) {
|
|
7
|
+
return (_jsx(Box, { "data-chat-markdown": "", sx: {
|
|
8
|
+
...streamdownMarkdownStyles,
|
|
9
|
+
...(density === 'compact'
|
|
10
|
+
? {
|
|
11
|
+
lineHeight: 1.4,
|
|
12
|
+
'& p': { marginTop: 0, marginBottom: '0.4em' },
|
|
13
|
+
'& p:last-child': { marginBottom: 0 },
|
|
14
|
+
}
|
|
15
|
+
: {}),
|
|
16
|
+
}, children: _jsx(Streamdown, { children: normalizeAssistantMarkdown(text) }) }));
|
|
17
|
+
}
|
|
18
|
+
export default ChatMarkdown;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode, type RefObject } from 'react';
|
|
2
|
+
import { type ChatDensity } from './ChatMarkdown';
|
|
2
3
|
import { type MarkedMcpServer } from '../marks';
|
|
3
4
|
import type { DisplayItem, AvatarConfig, RenderToolResult } from '../../types/chat';
|
|
4
5
|
import type { ChatMessage } from '../../types/messages';
|
|
@@ -67,6 +68,12 @@ export interface ChatMessageListProps {
|
|
|
67
68
|
* and runtime tools are found in the catalogues.
|
|
68
69
|
*/
|
|
69
70
|
mcpServers?: readonly MarkedMcpServer[];
|
|
71
|
+
/**
|
|
72
|
+
* How dense it draws: the chat's own (`comfortable`), or the floating
|
|
73
|
+
* assistant's balloon (`compact`: no avatars, smaller bubbles and tool
|
|
74
|
+
* rows, no turn footers). The same components either way.
|
|
75
|
+
*/
|
|
76
|
+
density?: ChatDensity;
|
|
70
77
|
}
|
|
71
78
|
type SubagentAvatarConfig = Required<Pick<AvatarConfig, 'userAvatar' | 'assistantAvatar' | 'showAvatars' | 'avatarSize' | 'userAvatarBg' | 'assistantAvatarBg'>>;
|
|
72
79
|
export interface SubagentChatPanelProps {
|
|
@@ -86,5 +93,5 @@ export interface SubagentChatPanelProps {
|
|
|
86
93
|
* approval decisions are sent over the shared monitoring socket.
|
|
87
94
|
*/
|
|
88
95
|
export declare function SubagentChatPanel({ toolCallId, subagentName: subagentNameHint, height, avatarConfig, }: SubagentChatPanelProps): React.ReactElement | null;
|
|
89
|
-
export declare function ChatMessageList({ displayItems, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, renderToolResult, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters, agentUsage, onRemoveItems, renderMessageFooter, mcpServers, }: ChatMessageListProps): import("react").JSX.Element;
|
|
96
|
+
export declare function ChatMessageList({ displayItems, isLoading, isStreaming, showLoadingIndicator, hideMessagesAfterToolUI, avatarConfig, padding, renderToolResult, emptyContent, messagesEndRef, onRespond, approvalConfig, showTurnFooters, agentUsage, onRemoveItems, renderMessageFooter, mcpServers, density, }: ChatMessageListProps): import("react").JSX.Element;
|
|
90
97
|
export {};
|