@datalayer/agent-runtimes 1.3.37 → 1.3.39

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.
@@ -11,14 +11,70 @@
11
11
  * previous turn), `assistant` as the reply arrives, `end` when the agent
12
12
  * stops. Readers hold the signal.
13
13
  *
14
+ * Beside the turn, the whole conversation (`conversation`), for a page that
15
+ * shows it as the Chat component does: the chat view hands it every change
16
+ * of its items, streaming included.
17
+ *
14
18
  * @module loop/plugins/chat/turnState
15
19
  */
16
20
  import { signal } from '@datalayer/reactor';
21
+ /** A message's words, whether its content is a string or parts. */
22
+ export function messageText(message) {
23
+ const content = message.content;
24
+ return typeof content === 'string'
25
+ ? content
26
+ : content
27
+ .map(part => typeof part === 'string'
28
+ ? part
29
+ : (part.text ?? ''))
30
+ .join('');
31
+ }
32
+ const isToolCall = (item) => item.type === 'tool-call';
33
+ /**
34
+ * The conversation as a page shows it: each person's and assistant's
35
+ * message with words in it, and each tool call by its name, its arguments
36
+ * and its result once it has one. A system message is the chat's own and is
37
+ * left out.
38
+ */
39
+ export function conversationOf(items) {
40
+ const entries = [];
41
+ for (const item of items) {
42
+ if (isToolCall(item)) {
43
+ entries.push({
44
+ role: 'tool',
45
+ name: item.toolName,
46
+ args: item.args ?? {},
47
+ ...(item.result === undefined ? {} : { result: item.result }),
48
+ });
49
+ continue;
50
+ }
51
+ if (item.role !== 'user' && item.role !== 'assistant') {
52
+ continue;
53
+ }
54
+ const text = messageText(item);
55
+ if (text.trim()) {
56
+ entries.push({ role: item.role, text });
57
+ }
58
+ }
59
+ return entries;
60
+ }
61
+ /** Whether two conversations say the same, entry by entry. */
62
+ const sameConversation = (one, other) => one.length === other.length &&
63
+ one.every((entry, index) => JSON.stringify(entry) === JSON.stringify(other[index]));
17
64
  const IDLE = { id: 0, status: 'idle' };
18
65
  export function createTurnFeed() {
19
66
  const turn = signal(IDLE);
67
+ const conversation = signal([]);
20
68
  return {
21
69
  turn,
70
+ conversation,
71
+ items: items => {
72
+ const next = conversationOf(items);
73
+ // Nothing new: readers do not re-render for an identical conversation.
74
+ if (!sameConversation(conversation.value, next)) {
75
+ conversation.value = next;
76
+ }
77
+ },
22
78
  begin: (user, thread) => {
23
79
  // The window's fill carries over: it is the conversation's, not the
24
80
  // turn's, and the footer under a fresh turn should not read empty
@@ -173,6 +173,17 @@ export type LoopPresetOptions = {
173
173
  * cells and documents.
174
174
  */
175
175
  editors?: boolean;
176
+ /**
177
+ * Whether the workspace holds a conversation at all.
178
+ *
179
+ * True by default. A host whose one view is a page of its own — an
180
+ * application that answers in its page and has no agent to talk to, a
181
+ * decision (LOOP R-02) — passes `false`: the chat and its composer, the
182
+ * agents, the models, the editors and the page layout all stay out, so
183
+ * that nothing is launched or fetched for a conversation nobody has, and
184
+ * the workspace opens on the view a host's plugin contributes.
185
+ */
186
+ conversation?: boolean;
176
187
  /**
177
188
  * The editor choice in the workspace header rather than above the chat.
178
189
  *
@@ -52,8 +52,23 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
52
52
  * be installed or signed into, and the switches a demonstration wants are off.
53
53
  */
54
54
  export function loopPlugins(options = {}) {
55
- const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, targetFixed, themeVariant, themeOverrides, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
55
+ const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, targetFixed, themeVariant, themeOverrides, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, conversation = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
56
56
  const defaultEditor = askedEditor ?? (editors ? 'notebook' : 'none');
57
+ // A workspace without a conversation: the theme, the shell's points, and
58
+ // the host's chrome; its view is a host's plugin's.
59
+ if (!conversation) {
60
+ return [
61
+ ThemePlugin,
62
+ configurePlugin(ShellPlugin, {
63
+ defaultEditor: 'none',
64
+ showSelector: false,
65
+ }),
66
+ ...(graph ? [GraphViewPlugin] : []),
67
+ ...(commandPalette ? [LoopCommandsPlugin] : []),
68
+ ...(pluginsPanel ? [PluginsPanelPlugin] : []),
69
+ ...(windowFrame ? [WindowFramePlugin] : []),
70
+ ];
71
+ }
57
72
  return [
58
73
  // The chat owns the editor beside it, so which one opens is its
59
74
  // configuration rather than the workspace's.
@@ -83,6 +83,8 @@ export declare class AGUIAdapter extends BaseProtocolAdapter {
83
83
  provider: string;
84
84
  accessToken: string;
85
85
  }>;
86
+ /** What goes with the run besides the conversation (AG-UI's). */
87
+ forwardedProps?: Record<string, unknown>;
86
88
  }): Promise<void>;
87
89
  /**
88
90
  * Send tool result back through AG-UI and continue the conversation.
@@ -191,7 +191,7 @@ export class AGUIAdapter extends BaseProtocolAdapter {
191
191
  skills: options.skills,
192
192
  }),
193
193
  context: [],
194
- forwardedProps: null,
194
+ forwardedProps: options?.forwardedProps ?? null,
195
195
  // Include model for per-request model override
196
196
  ...(options?.model && { model: options.model }),
197
197
  // Include identities for tool execution with OAuth tokens
@@ -654,7 +654,8 @@ export interface ChatBaseProps {
654
654
  * Called again with `null` when the chat can no longer send.
655
655
  */
656
656
  onSendReady?: (controls: {
657
- send: (message: string) => void;
657
+ /** Send a message; `forwardedProps` go with its run (AG-UI's). */
658
+ send: (message: string, forwardedProps?: Record<string, unknown>) => void;
658
659
  stop: () => void;
659
660
  /** Start the conversation over — what the header's + does. */
660
661
  newChat: () => void;
@@ -142,6 +142,8 @@ export interface ProtocolAdapter {
142
142
  provider: string;
143
143
  accessToken: string;
144
144
  }>;
145
+ /** What goes with the run besides the conversation (AG-UI's `forwardedProps`). */
146
+ forwardedProps?: Record<string, unknown>;
145
147
  }): Promise<void>;
146
148
  /**
147
149
  * Send tool execution result back
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.37",
3
+ "version": "1.3.39",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",