@datalayer/agent-runtimes 0.0.2 → 0.0.4

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 (34) hide show
  1. package/lib/components/chat/components/AgentDetails.js +65 -2
  2. package/lib/components/chat/components/ChatFloating.d.ts +13 -3
  3. package/lib/components/chat/components/ChatFloating.js +20 -9
  4. package/lib/components/chat/components/base/ChatBase.d.ts +25 -0
  5. package/lib/components/chat/components/base/ChatBase.js +208 -18
  6. package/lib/components/chat/components/index.d.ts +1 -1
  7. package/lib/components/chat/components/parts/TextPart.js +5 -1
  8. package/lib/components/chat/index.d.ts +1 -1
  9. package/lib/components/chat/protocols/A2AAdapter.d.ts +2 -0
  10. package/lib/components/chat/protocols/A2AAdapter.js +9 -0
  11. package/lib/components/chat/protocols/ACPAdapter.d.ts +2 -0
  12. package/lib/components/chat/protocols/ACPAdapter.js +13 -0
  13. package/lib/components/chat/protocols/AGUIAdapter.d.ts +2 -0
  14. package/lib/components/chat/protocols/AGUIAdapter.js +5 -0
  15. package/lib/components/chat/protocols/BaseProtocolAdapter.d.ts +2 -0
  16. package/lib/components/chat/protocols/VercelAIAdapter.d.ts +6 -0
  17. package/lib/components/chat/protocols/VercelAIAdapter.js +10 -0
  18. package/lib/components/chat/types/protocol.d.ts +4 -0
  19. package/lib/examples/AgentRuntimeCustomExample.d.ts +2 -1
  20. package/lib/examples/AgentRuntimeCustomExample.js +120 -5
  21. package/lib/examples/AgentRuntimeLexicalExample.js +1 -1
  22. package/lib/examples/components/AgentConfiguration.d.ts +22 -0
  23. package/lib/examples/components/AgentConfiguration.js +39 -3
  24. package/lib/examples/components/index.d.ts +1 -1
  25. package/lib/hooks/useNotebookAIAgent.d.ts +2 -2
  26. package/lib/hooks/useNotebookAIAgent.js +26 -9
  27. package/lib/index.d.ts +1 -0
  28. package/lib/index.js +1 -0
  29. package/lib/state/substates/AIAgentState.d.ts +80 -10
  30. package/lib/state/substates/AIAgentState.js +89 -23
  31. package/lib/stubs/keytar.d.ts +30 -0
  32. package/lib/stubs/keytar.js +28 -0
  33. package/package.json +9 -7
  34. package/style/base.css +1 -43
@@ -221,9 +221,14 @@ export class ACPAdapter extends BaseProtocolAdapter {
221
221
  .filter(c => c.type === 'text')
222
222
  .map(c => c.text || '')
223
223
  .join('');
224
+ if (_options?.model) {
225
+ console.log('[ACPAdapter] Sending with model:', _options.model);
226
+ }
224
227
  await this.sendRequest(AGENT_METHODS.session_prompt, {
225
228
  sessionId: this.session.sessionId,
226
229
  content: [{ type: 'text', text: content }],
230
+ // Include model for per-request model override
231
+ ...(_options?.model && { metadata: { model: _options.model } }),
227
232
  });
228
233
  }
229
234
  catch (error) {
@@ -450,6 +455,14 @@ export class ACPAdapter extends BaseProtocolAdapter {
450
455
  timestamp: new Date(),
451
456
  });
452
457
  }
458
+ // Handle error events - emit as error, not as message
459
+ if (params.error) {
460
+ this.emit({
461
+ type: 'error',
462
+ error: new Error(params.error),
463
+ timestamp: new Date(),
464
+ });
465
+ }
453
466
  }
454
467
  /**
455
468
  * Handle permission request from agent
@@ -52,6 +52,8 @@ export declare class AGUIAdapter extends BaseProtocolAdapter {
52
52
  metadata?: Record<string, unknown>;
53
53
  /** Full conversation history to send with the message */
54
54
  messages?: ChatMessage[];
55
+ /** Model to use for this request (overrides agent default) */
56
+ model?: string;
55
57
  }): Promise<void>;
56
58
  /**
57
59
  * Send tool result back through AG-UI and continue the conversation
@@ -135,7 +135,12 @@ export class AGUIAdapter extends BaseProtocolAdapter {
135
135
  tools: options?.tools || [],
136
136
  context: [],
137
137
  forwardedProps: null,
138
+ // Include model for per-request model override
139
+ ...(options?.model && { model: options.model }),
138
140
  };
141
+ if (options?.model) {
142
+ console.log('[AGUIAdapter] Sending with model:', options.model);
143
+ }
139
144
  try {
140
145
  const response = await fetch(this.aguiConfig.baseUrl, {
141
146
  method: 'POST',
@@ -35,6 +35,8 @@ export declare abstract class BaseProtocolAdapter implements ProtocolAdapter {
35
35
  metadata?: Record<string, unknown>;
36
36
  /** Full conversation history to send with the message */
37
37
  messages?: ChatMessage[];
38
+ /** Model to use for this request (overrides agent default) */
39
+ model?: string;
38
40
  }): Promise<void>;
39
41
  /**
40
42
  * Send tool execution result back
@@ -57,6 +57,12 @@ export declare class VercelAIAdapter extends BaseProtocolAdapter {
57
57
  tools?: ToolDefinition[];
58
58
  threadId?: string;
59
59
  metadata?: Record<string, unknown>;
60
+ /** Model to use for this request (overrides agent default) */
61
+ model?: string;
62
+ /** Full conversation history to send with the message */
63
+ messages?: ChatMessage[];
64
+ /** Builtin tools / MCP tools to enable for this request */
65
+ builtinTools?: string[];
60
66
  }): Promise<void>;
61
67
  /**
62
68
  * Parse SSE stream from Vercel AI
@@ -105,7 +105,17 @@ export class VercelAIAdapter extends BaseProtocolAdapter {
105
105
  trigger: 'submit-message',
106
106
  // Optional fields based on Pydantic AI's Vercel adapter
107
107
  ...(options?.tools && { tools: options.tools }),
108
+ // Model override for per-request model selection
109
+ ...(options?.model && { model: options.model }),
110
+ // Builtin tools / MCP tools to enable
111
+ ...(options?.builtinTools &&
112
+ options.builtinTools.length > 0 && {
113
+ builtinTools: options.builtinTools,
114
+ }),
108
115
  };
116
+ if (options?.model) {
117
+ console.log('[VercelAIAdapter] Sending with model:', options.model);
118
+ }
109
119
  // Merge custom headers with defaults
110
120
  const headers = {
111
121
  'Content-Type': 'application/json',
@@ -115,6 +115,8 @@ export interface ProtocolAdapter {
115
115
  metadata?: Record<string, unknown>;
116
116
  /** Full conversation history to send with the message */
117
117
  messages?: ChatMessage[];
118
+ /** Model to use for this request (overrides agent default) */
119
+ model?: string;
118
120
  }): Promise<void>;
119
121
  /**
120
122
  * Send tool execution result back
@@ -161,6 +163,8 @@ export declare namespace AGUI {
161
163
  content: string;
162
164
  }>;
163
165
  forwardedProps: Record<string, unknown> | null;
166
+ /** Optional model override for per-request model selection */
167
+ model?: string;
164
168
  }
165
169
  interface Event {
166
170
  type: string;
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Agent Runtime Custom Example Component
3
3
  *
4
- * Demonstrates the unified Chat component with Simple transport
4
+ * Demonstrates the unified Chat component with AG-UI transport
5
5
  * and all necessary providers:
6
6
  * - QueryClientProvider for data fetching
7
+ * - Auto-creates agent on the server if it doesn't exist
7
8
  */
8
9
  declare const AgentRuntimeCustomExample: React.FC;
9
10
  export default AgentRuntimeCustomExample;
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * Copyright (c) 2025-2026 Datalayer, Inc.
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
- import { Text } from '@primer/react';
6
+ import { useCallback, useEffect, useState } from 'react';
7
+ import { Spinner, Text } from '@primer/react';
7
8
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8
9
  import { Box } from '@datalayer/primer-addons';
9
10
  import { datalayerTheme, DatalayerThemeProvider } from '@datalayer/core';
@@ -19,14 +20,107 @@ const queryClient = new QueryClient({
19
20
  },
20
21
  },
21
22
  });
23
+ const AGENT_NAME = 'datalayer-assistant';
24
+ const BASE_URL = 'http://localhost:8765';
25
+ /**
26
+ * Hook to ensure the agent exists on the server
27
+ * Creates the agent if it doesn't exist
28
+ */
29
+ function useEnsureAgent() {
30
+ const [agentId, setAgentId] = useState(null);
31
+ const [isLoading, setIsLoading] = useState(true);
32
+ const [error, setError] = useState(null);
33
+ const createAgent = useCallback(async () => {
34
+ try {
35
+ const response = await fetch(`${BASE_URL}/api/v1/agents`, {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: JSON.stringify({
41
+ name: AGENT_NAME,
42
+ description: 'Datalayer AI Assistant - Your helpful coding companion',
43
+ agent_library: 'pydantic-ai',
44
+ transport: 'ag-ui',
45
+ model: 'openai:gpt-4o-mini',
46
+ system_prompt: `You are Datalayer Assistant, a helpful AI assistant specialized in data science, Python programming, and Jupyter notebooks.
47
+
48
+ You can help users with:
49
+ - Writing and debugging Python code
50
+ - Data analysis with pandas, numpy, and other libraries
51
+ - Creating visualizations with matplotlib, plotly, etc.
52
+ - Machine learning with scikit-learn, pytorch, etc.
53
+ - General programming questions
54
+
55
+ Be concise, helpful, and provide working code examples when appropriate.`,
56
+ }),
57
+ });
58
+ if (!response.ok) {
59
+ const errorData = await response
60
+ .json()
61
+ .catch(() => ({ detail: 'Unknown error' }));
62
+ throw new Error(errorData.detail || `Failed to create agent: ${response.status}`);
63
+ }
64
+ const data = await response.json();
65
+ console.log('[AgentRuntimeCustomExample] Agent created:', data);
66
+ return data.id;
67
+ }
68
+ catch (err) {
69
+ console.error('[AgentRuntimeCustomExample] Error creating agent:', err);
70
+ throw err;
71
+ }
72
+ }, []);
73
+ const checkOrCreateAgent = useCallback(async () => {
74
+ setIsLoading(true);
75
+ setError(null);
76
+ try {
77
+ // First, try to find existing agent by name
78
+ const listResponse = await fetch(`${BASE_URL}/api/v1/agents`);
79
+ if (listResponse.ok) {
80
+ const data = await listResponse.json();
81
+ // API may return { agents: [...] } or just [...]
82
+ const agents = Array.isArray(data) ? data : data.agents || [];
83
+ const existingAgent = agents.find((a) => a.name === AGENT_NAME);
84
+ if (existingAgent) {
85
+ console.log('[AgentRuntimeCustomExample] Found existing agent:', existingAgent.id);
86
+ setAgentId(existingAgent.id);
87
+ setIsLoading(false);
88
+ return;
89
+ }
90
+ }
91
+ // Agent doesn't exist, create it
92
+ console.log('[AgentRuntimeCustomExample] Creating new agent...');
93
+ const newAgentId = await createAgent();
94
+ if (newAgentId) {
95
+ setAgentId(newAgentId);
96
+ }
97
+ else {
98
+ setError('Failed to create agent');
99
+ }
100
+ }
101
+ catch (err) {
102
+ const errorMessage = err instanceof Error ? err.message : 'Failed to initialize agent';
103
+ setError(errorMessage);
104
+ }
105
+ finally {
106
+ setIsLoading(false);
107
+ }
108
+ }, [createAgent]);
109
+ useEffect(() => {
110
+ checkOrCreateAgent();
111
+ }, [checkOrCreateAgent]);
112
+ return { agentId, isLoading, error, retry: checkOrCreateAgent };
113
+ }
22
114
  /**
23
115
  * Agent Runtime Custom Example Component
24
116
  *
25
- * Demonstrates the unified Chat component with Simple transport
117
+ * Demonstrates the unified Chat component with AG-UI transport
26
118
  * and all necessary providers:
27
119
  * - QueryClientProvider for data fetching
120
+ * - Auto-creates agent on the server if it doesn't exist
28
121
  */
29
122
  const AgentRuntimeCustomExample = () => {
123
+ const { agentId, isLoading, error, retry } = useEnsureAgent();
30
124
  return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(DatalayerThemeProvider, { theme: datalayerTheme, children: _jsxs(Box, { sx: {
31
125
  display: 'flex',
32
126
  flexDirection: 'column',
@@ -41,12 +135,33 @@ const AgentRuntimeCustomExample = () => {
41
135
  fontWeight: 'bold',
42
136
  display: 'block',
43
137
  marginBottom: 1,
44
- }, children: "Agent Runtime Custom Example" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Interactive chat interface with AI assistance" })] }), _jsx(Box, { as: "main", sx: {
138
+ }, children: "Datalayer Assistant" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Interactive chat interface with AI assistance" })] }), _jsx(Box, { as: "main", sx: {
45
139
  flex: 1,
46
140
  overflow: 'hidden',
47
141
  display: 'flex',
48
142
  flexDirection: 'column',
49
- }, children: _jsx(Chat, { transport: "vercel-ai-jupyter", showModelSelector: true, showToolsMenu: true, height: "100%", suggestions: [
143
+ }, children: isLoading ? (_jsxs(Box, { sx: {
144
+ display: 'flex',
145
+ flexDirection: 'column',
146
+ alignItems: 'center',
147
+ justifyContent: 'center',
148
+ height: '100%',
149
+ gap: 3,
150
+ }, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Initializing agent..." })] })) : error ? (_jsxs(Box, { sx: {
151
+ display: 'flex',
152
+ flexDirection: 'column',
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ height: '100%',
156
+ gap: 3,
157
+ p: 4,
158
+ }, children: [_jsx(Text, { sx: { color: 'danger.fg', fontWeight: 'bold' }, children: "Failed to initialize agent" }), _jsx(Text, { sx: { color: 'fg.muted', textAlign: 'center' }, children: error }), _jsx(Text, { as: "button", onClick: retry, sx: {
159
+ color: 'accent.fg',
160
+ cursor: 'pointer',
161
+ textDecoration: 'underline',
162
+ border: 'none',
163
+ background: 'none',
164
+ }, children: "Retry" })] })) : agentId ? (_jsx(Chat, { transport: "ag-ui", agentId: agentId, showModelSelector: true, showToolsMenu: true, height: "100%", suggestions: [
50
165
  {
51
166
  title: '👋 Say hello',
52
167
  message: 'Hello! What can you help me with today?',
@@ -63,6 +178,6 @@ const AgentRuntimeCustomExample = () => {
63
178
  title: '📊 Data analysis',
64
179
  message: 'How do I analyze data with pandas?',
65
180
  },
66
- ] }) })] }) }) }));
181
+ ] })) : null })] }) }) }));
67
182
  };
68
183
  export default AgentRuntimeCustomExample;
@@ -239,7 +239,7 @@ function LexicalWithChat({ content, serviceManager, }) {
239
239
  color: 'danger.fg',
240
240
  borderRadius: 2,
241
241
  maxWidth: 300,
242
- }, children: [_jsx("strong", { children: "Error:" }), " ", error] })), isReady && (_jsx(ChatFloating, { endpoint: AG_UI_ENDPOINT, title: "Lexical AI Agent Runtime", description: "Hi! I can help you edit documents. Try: 'Insert a heading', 'Add a code block', or 'Create a list'", defaultOpen: true, defaultViewMode: "panel", position: "bottom-right", brandColor: "#7c3aed", tools: tools, useStore: false, suggestions: [
242
+ }, children: [_jsx("strong", { children: "Error:" }), " ", error] })), isReady && (_jsx(ChatFloating, { endpoint: AG_UI_ENDPOINT, title: "Lexical AI Agent Runtime", description: "Hi! I can help you edit documents. Try: 'Insert a heading', 'Add a code block', or 'Create a list'", defaultOpen: true, defaultViewMode: "panel", position: "bottom-right", brandColor: "#7c3aed", tools: tools, useStore: false, showModelSelector: true, showToolsMenu: true, suggestions: [
243
243
  {
244
244
  title: 'Insert heading',
245
245
  message: 'Insert a heading that says "Welcome"',
@@ -1,6 +1,28 @@
1
1
  import React from 'react';
2
2
  import type { Agent } from '../stores/examplesStore';
3
3
  import type { Transport, Extension } from '../../components/chat';
4
+ /**
5
+ * MCP Server Tool type
6
+ */
7
+ export interface MCPServerTool {
8
+ name: string;
9
+ description?: string;
10
+ enabled: boolean;
11
+ }
12
+ /**
13
+ * MCP Server configuration from backend
14
+ */
15
+ export interface MCPServerConfig {
16
+ id: string;
17
+ name: string;
18
+ url?: string;
19
+ enabled: boolean;
20
+ tools: MCPServerTool[];
21
+ command?: string;
22
+ args?: string[];
23
+ isAvailable?: boolean;
24
+ transport?: string;
25
+ }
4
26
  type AgentLibrary = 'pydantic-ai' | 'langchain' | 'jupyter-ai';
5
27
  export type { AgentLibrary };
6
28
  export type { Transport };
@@ -1,5 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Text, TextInput, Button, FormControl, Select, CheckboxGroup, Checkbox, Spinner, Flash, } from '@primer/react';
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import React from 'react';
7
+ import { Text, TextInput, Button, FormControl, Select, Checkbox, Spinner, Flash, Label, ActionList, } from '@primer/react';
8
+ import { CheckIcon, XIcon, ToolsIcon } from '@primer/octicons-react';
9
+ import { useQuery } from '@tanstack/react-query';
3
10
  import { Box } from '@datalayer/primer-addons';
4
11
  const AGENT_LIBRARIES = [
5
12
  {
@@ -65,6 +72,21 @@ const EXTENSIONS = [
65
72
  * Form for configuring agent connection settings.
66
73
  */
67
74
  export const AgentConfiguration = ({ agentLibrary, transport, extensions, wsUrl, baseUrl, agentName, agents, selectedAgentId, isCreatingAgent = false, createError = null, onAgentLibraryChange, onTransportChange, onExtensionsChange, onWsUrlChange, onBaseUrlChange, onAgentNameChange, onAgentSelect, onConnect, }) => {
75
+ // Fetch MCP servers configuration from the backend
76
+ const configQuery = useQuery({
77
+ queryKey: ['agent-config', baseUrl],
78
+ queryFn: async () => {
79
+ const response = await fetch(`${baseUrl}/api/v1/configure`);
80
+ if (!response.ok) {
81
+ throw new Error('Failed to fetch configuration');
82
+ }
83
+ return response.json();
84
+ },
85
+ enabled: !!baseUrl,
86
+ staleTime: 1000 * 60 * 5, // 5 minutes
87
+ retry: 1,
88
+ });
89
+ const mcpServers = configQuery.data?.mcpServers || [];
68
90
  // Determine which extensions are enabled based on transport
69
91
  const isExtensionEnabled = (ext) => {
70
92
  if (selectedAgentId !== 'new-agent')
@@ -95,7 +117,7 @@ export const AgentConfiguration = ({ agentLibrary, transport, extensions, wsUrl,
95
117
  fontWeight: 'bold',
96
118
  display: 'block',
97
119
  marginBottom: 3,
98
- }, children: "Connection Settings" }), _jsxs(FormControl, { sx: { marginBottom: 3 }, children: [_jsx(FormControl.Label, { children: "Available Agents" }), _jsxs(Select, { value: selectedAgentId, onChange: e => onAgentSelect(e.target.value), sx: { width: '100%' }, children: [_jsx(Select.Option, { value: "new-agent", children: "+ New Agent..." }), agents.map(agent => (_jsxs(Select.Option, { value: agent.id, children: [agent.status === 'running' && '● ', agent.name] }, agent.id)))] }), _jsx(FormControl.Caption, { children: selectedAgentId === 'new-agent'
120
+ }, children: "Create a new Agent" }), _jsxs(FormControl, { sx: { marginBottom: 3 }, children: [_jsx(FormControl.Label, { children: "Available Agents" }), _jsxs(Select, { value: selectedAgentId, onChange: e => onAgentSelect(e.target.value), sx: { width: '100%' }, children: [_jsx(Select.Option, { value: "new-agent", children: "+ New Agent..." }), agents.map(agent => (_jsxs(Select.Option, { value: agent.id, children: [agent.status === 'running' && '● ', agent.name] }, agent.id)))] }), _jsx(FormControl.Caption, { children: selectedAgentId === 'new-agent'
99
121
  ? 'Configure a new custom agent'
100
122
  : 'Selected agent - form fields below are disabled' })] }), _jsxs(Box, { sx: { display: 'flex', gap: 3, marginBottom: 3 }, children: [_jsxs(FormControl, { sx: { flex: 1 }, children: [_jsx(FormControl.Label, { children: "Agent Library" }), _jsx(Select, { value: agentLibrary, onChange: e => onAgentLibraryChange(e.target.value), disabled: selectedAgentId !== 'new-agent', sx: { width: '100%' }, children: AGENT_LIBRARIES.map(lib => (_jsxs(Select.Option, { value: lib.value, disabled: lib.disabled, children: [lib.label, lib.disabled && ' (Coming Soon)'] }, lib.value))) })] }), _jsxs(FormControl, { sx: { flex: 1 }, children: [_jsx(FormControl.Label, { children: "Transport" }), _jsx(Select, { value: transport, onChange: e => onTransportChange(e.target.value), disabled: selectedAgentId !== 'new-agent', sx: { width: '100%' }, children: TRANSPORTS.map(t => (_jsx(Select.Option, { value: t.value, children: t.label }, t.value))) })] }), _jsxs(FormControl, { sx: { flex: 1 }, children: [_jsx(FormControl.Label, { children: "Extensions" }), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: EXTENSIONS.map(ext => (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Checkbox, { value: ext.value, checked: extensions.includes(ext.value), disabled: !isExtensionEnabled(ext.value), onChange: e => handleExtensionChange(ext.value, e.target.checked) }), _jsx(Text, { children: ext.label })] }, ext.value))) })] })] }), _jsxs(FormControl, { sx: { marginBottom: 3 }, children: [_jsx(FormControl.Label, { children: transport === 'acp' ? 'WebSocket URL' : 'Base URL' }), _jsx(TextInput, { value: transport === 'acp' ? wsUrl : baseUrl, onChange: e => transport === 'acp'
101
123
  ? onWsUrlChange(e.target.value)
@@ -103,7 +125,21 @@ export const AgentConfiguration = ({ agentLibrary, transport, extensions, wsUrl,
103
125
  ? 'ws://localhost:8000/api/v1/acp/ws'
104
126
  : 'http://localhost:8000', sx: { width: '100%' } }), _jsx(FormControl.Caption, { children: transport === 'acp'
105
127
  ? 'The WebSocket endpoint of your agent-runtimes server'
106
- : 'The base URL of your agent-runtimes server' })] }), _jsxs(FormControl, { sx: { marginBottom: 3 }, children: [_jsx(FormControl.Label, { children: "Agent Name" }), _jsx(TextInput, { value: agentName, onChange: e => onAgentNameChange(e.target.value), disabled: selectedAgentId !== 'new-agent', placeholder: "demo-agent", sx: { width: '100%' } }), _jsx(FormControl.Caption, { children: "The name of the agent to connect to" })] }), _jsxs(CheckboxGroup, { sx: { marginBottom: 3 }, disabled: true, children: [_jsx(CheckboxGroup.Label, { children: "MCP Servers (Coming Soon)" }), _jsx(CheckboxGroup.Caption, { children: "Select MCP servers to connect to" }), _jsxs(FormControl, { disabled: true, children: [_jsx(Checkbox, { value: "github", defaultChecked: true, disabled: true }), _jsx(FormControl.Label, { children: "GitHub" })] }), _jsxs(FormControl, { disabled: true, children: [_jsx(Checkbox, { value: "anaconda", disabled: true }), _jsx(FormControl.Label, { children: "Anaconda" })] }), _jsxs(FormControl, { disabled: true, children: [_jsx(Checkbox, { value: "tavily", disabled: true }), _jsx(FormControl.Label, { children: "Tavily" })] })] }), createError && (_jsx(Flash, { variant: "danger", sx: { marginBottom: 3 }, children: createError })), _jsx(Button, { variant: "primary", onClick: onConnect, disabled: isCreatingAgent ||
128
+ : 'The base URL of your agent-runtimes server' })] }), _jsxs(FormControl, { sx: { marginBottom: 3 }, children: [_jsx(FormControl.Label, { children: "Agent Name" }), _jsx(TextInput, { value: agentName, onChange: e => onAgentNameChange(e.target.value), disabled: selectedAgentId !== 'new-agent', placeholder: "demo-agent", sx: { width: '100%' } }), _jsx(FormControl.Caption, { children: "The name of the agent to connect to" })] }), _jsxs(Box, { sx: {
129
+ marginBottom: 3,
130
+ padding: 3,
131
+ border: '1px solid',
132
+ borderColor: 'border.default',
133
+ borderRadius: 2,
134
+ backgroundColor: 'canvas.default',
135
+ }, children: [_jsxs(Box, { sx: {
136
+ display: 'flex',
137
+ alignItems: 'center',
138
+ gap: 2,
139
+ marginBottom: 2,
140
+ }, children: [_jsx(ToolsIcon, { size: 16 }), _jsx(Text, { sx: { fontSize: 1, fontWeight: 'bold' }, children: "MCP Servers" }), configQuery.isLoading && _jsx(Spinner, { size: "small" })] }), configQuery.isError && (_jsx(Flash, { variant: "warning", sx: { marginBottom: 2 }, children: _jsx(Text, { sx: { fontSize: 0 }, children: "Unable to fetch MCP servers. Check that the server is running." }) })), mcpServers.length === 0 &&
141
+ !configQuery.isLoading &&
142
+ !configQuery.isError && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "No MCP servers configured." })), mcpServers.length > 0 && (_jsx(ActionList, { children: mcpServers.map((server, index) => (_jsxs(React.Fragment, { children: [index > 0 && _jsx(ActionList.Divider, {}), _jsxs(ActionList.Item, { disabled: true, children: [_jsx(ActionList.LeadingVisual, { children: server.isAvailable ? (_jsx(CheckIcon, { size: 16 })) : (_jsx(XIcon, { size: 16 })) }), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 1 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: server.name }), _jsx(Label, { variant: server.isAvailable ? 'success' : 'secondary', size: "small", children: server.isAvailable ? 'Available' : 'Not Available' })] }), server.tools.length > 0 && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Tools: ", server.tools.map(t => t.name).join(', ')] }))] })] })] }, server.id))) }))] }), createError && (_jsx(Flash, { variant: "danger", sx: { marginBottom: 3 }, children: createError })), _jsx(Button, { variant: "primary", onClick: onConnect, disabled: isCreatingAgent ||
107
143
  !agentName ||
108
144
  (transport === 'acp' ? !wsUrl : !baseUrl), sx: { width: '100%' }, children: isCreatingAgent ? (_jsxs(Box, { sx: {
109
145
  display: 'flex',
@@ -8,4 +8,4 @@ export { TimeTravel } from './TimeTravel';
8
8
  export { LexicalEditor } from './LexicalEditor';
9
9
  export { Rating } from './Rating';
10
10
  export { AgentConfiguration, AGENT_LIBRARIES, TRANSPORTS, EXTENSIONS, } from './AgentConfiguration';
11
- export type { AgentLibrary, Transport, Extension } from './AgentConfiguration';
11
+ export type { AgentLibrary, Transport, Extension, MCPServerConfig, MCPServerTool, } from './AgentConfiguration';
@@ -1,8 +1,8 @@
1
- import type { IAIAgent } from '../models';
1
+ import type { Agent } from '../state';
2
2
  /**
3
3
  * Get the document AI Agent if any.
4
4
  *
5
5
  * It handles checking the AI Agent is alive so it should only be use once per document.
6
6
  */
7
- export declare function useNotebookAIAgent(notebookId: string): IAIAgent | undefined;
7
+ export declare function useNotebookAIAgent(notebookId: string): Agent | undefined;
8
8
  export default useNotebookAIAgent;
@@ -3,7 +3,7 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { useEffect } from 'react';
6
- import { useAIAgentStore } from '../state';
6
+ import { useAgentStore } from '../state';
7
7
  import { useAIAgents } from './useAgents';
8
8
  /**
9
9
  * Get the document AI Agent if any.
@@ -12,7 +12,10 @@ import { useAIAgents } from './useAgents';
12
12
  */
13
13
  export function useNotebookAIAgent(notebookId) {
14
14
  const { getAIAgent } = useAIAgents();
15
- const { aiAgents, addAIAgent, deleteAIAgent, updateAIAgent } = useAIAgentStore();
15
+ const agents = useAgentStore(state => state.agents);
16
+ const upsertAgent = useAgentStore(state => state.upsertAgent);
17
+ const deleteAgent = useAgentStore(state => state.deleteAgent);
18
+ const getAgentById = useAgentStore(state => state.getAgentById);
16
19
  // Check AI Agent is alive.
17
20
  useEffect(() => {
18
21
  let abortController;
@@ -23,25 +26,39 @@ export function useNotebookAIAgent(notebookId) {
23
26
  signal: abortController.signal,
24
27
  });
25
28
  if (!response.success) {
26
- deleteAIAgent(notebookId);
29
+ deleteAgent(notebookId);
27
30
  return;
28
31
  }
29
- const currentAgent = aiAgents.find(agent => agent.documentId === notebookId);
32
+ const currentAgent = getAgentById(notebookId);
30
33
  const runtimeId = response.agent.runtime?.id;
31
34
  if (currentAgent) {
35
+ // Update existing agent if runtime changed
32
36
  if (currentAgent.runtimeId !== runtimeId) {
33
- updateAIAgent(notebookId, runtimeId);
37
+ upsertAgent({
38
+ id: notebookId,
39
+ baseUrl: currentAgent.baseUrl,
40
+ transport: currentAgent.transport,
41
+ runtimeId,
42
+ status: 'running',
43
+ });
34
44
  }
35
45
  }
36
46
  else {
37
- addAIAgent({
47
+ // Add new agent - assume it's a notebook agent with document tracking
48
+ upsertAgent({
49
+ id: notebookId,
50
+ name: `Notebook ${notebookId}`,
51
+ description: 'AI agent for notebook',
52
+ baseUrl: '', // Will be set by the actual agent implementation
53
+ transport: 'vercel-ai', // Default transport for notebook agents
38
54
  documentId: notebookId,
39
55
  runtimeId,
56
+ status: 'running',
40
57
  });
41
58
  }
42
59
  }
43
60
  catch (r) {
44
- deleteAIAgent(notebookId);
61
+ deleteAgent(notebookId);
45
62
  }
46
63
  };
47
64
  const refreshInterval = setInterval(refreshAIAgent, 60_000);
@@ -49,8 +66,8 @@ export function useNotebookAIAgent(notebookId) {
49
66
  abortController?.abort('Component unmounted');
50
67
  clearInterval(refreshInterval);
51
68
  };
52
- }, [aiAgents, notebookId]);
53
- const aiAgent = aiAgents.find(aiAgent => aiAgent.documentId === notebookId);
69
+ }, [agents, notebookId, getAIAgent, deleteAgent, getAgentById, upsertAgent]);
70
+ const aiAgent = getAgentById(notebookId);
54
71
  return aiAgent;
55
72
  }
56
73
  export default useNotebookAIAgent;
package/lib/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './components';
2
+ export * from './state';
package/lib/index.js CHANGED
@@ -3,3 +3,4 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  export * from './components';
6
+ export * from './state';
@@ -1,11 +1,81 @@
1
- import type { IAIAgent } from '../../models';
2
- export type AIAgentState = {
3
- aiAgents: readonly IAIAgent[];
4
- addAIAgent: (aiAgent: IAIAgent) => void;
5
- deleteAIAgent: (documentId: string) => void;
6
- updateAIAgent: (documentId: string, runtimeId?: string) => void;
1
+ import type { Transport } from '../../components/chat/components/Chat';
2
+ export type AgentStatus = 'running' | 'paused' | 'initializing' | 'error';
3
+ export type { Transport };
4
+ /**
5
+ * Unified Agent model combining runtime tracking and UI state
6
+ */
7
+ export interface Agent {
8
+ /** Unique agent identifier */
9
+ id: string;
10
+ /** Display name */
11
+ name: string;
12
+ /** Agent description */
13
+ description: string;
14
+ /** Base URL for the agent (for Jupyter: baseUrl, for FastAPI: baseUrl) */
15
+ baseUrl: string;
16
+ /** Transport protocol used */
17
+ transport: Transport;
18
+ /** Current status */
19
+ status: AgentStatus;
20
+ /** Last error message if status is 'error' */
21
+ error?: string | null;
22
+ /** Timestamp of last update */
23
+ lastUpdated: number;
24
+ /** Document ID (for document-based agents) */
25
+ documentId?: string;
26
+ /** Runtime ID (for Jupyter kernel-based agents) */
27
+ runtimeId?: string;
28
+ /** Author name (optional, for display) */
29
+ author?: string;
30
+ /** Avatar URL (optional, for display) */
31
+ avatarUrl?: string;
32
+ }
33
+ export type AgentState = {
34
+ /** All registered agents */
35
+ agents: readonly Agent[];
36
+ /** Add or update an agent */
37
+ upsertAgent: (agent: Partial<Agent> & {
38
+ id: string;
39
+ baseUrl: string;
40
+ transport: Transport;
41
+ }) => void;
42
+ /** Get agent by ID */
43
+ getAgentById: (id: string) => Agent | undefined;
44
+ /** Get agent by baseUrl and transport */
45
+ getAgentByUrl: (baseUrl: string, transport: Transport) => Agent | undefined;
46
+ /** Update agent status */
47
+ updateAgentStatus: (id: string, status: AgentStatus, error?: string | null) => void;
48
+ /** Toggle agent status between running/paused */
49
+ toggleAgentStatus: (id: string) => void;
50
+ /** Delete an agent */
51
+ deleteAgent: (id: string) => void;
52
+ /** Clear all agents */
53
+ clearAgents: () => void;
7
54
  };
8
- export declare const aiAgentStore: import("zustand").StoreApi<AIAgentState>;
9
- export declare function useAIAgentStore(): AIAgentState;
10
- export declare function useAIAgentStore<T>(selector: (state: AIAgentState) => T): T;
11
- export default useAIAgentStore;
55
+ export declare const agentStore: Omit<import("zustand").StoreApi<AgentState>, "persist"> & {
56
+ persist: {
57
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>) => void;
58
+ clearStorage: () => void;
59
+ rehydrate: () => Promise<void> | void;
60
+ hasHydrated: () => boolean;
61
+ onHydrate: (fn: (state: AgentState) => void) => () => void;
62
+ onFinishHydration: (fn: (state: AgentState) => void) => () => void;
63
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>;
64
+ };
65
+ };
66
+ export declare function useAgentStore(): AgentState;
67
+ export declare function useAgentStore<T>(selector: (state: AgentState) => T): T;
68
+ export type AIAgentState = AgentState;
69
+ export declare const aiAgentStore: Omit<import("zustand").StoreApi<AgentState>, "persist"> & {
70
+ persist: {
71
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>) => void;
72
+ clearStorage: () => void;
73
+ rehydrate: () => Promise<void> | void;
74
+ hasHydrated: () => boolean;
75
+ onHydrate: (fn: (state: AgentState) => void) => () => void;
76
+ onFinishHydration: (fn: (state: AgentState) => void) => () => void;
77
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>;
78
+ };
79
+ };
80
+ export declare const useAIAgentStore: typeof useAgentStore;
81
+ export default useAgentStore;