@datalayer/agent-runtimes 1.5.16 → 1.5.18
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/dist-embed/chunks/ChatView-Bz0eyWo6.js +32 -0
- package/dist-embed/datalayer-app-main.js +102 -102
- package/lib/apps/apps/appspec.d.ts +36 -4
- package/lib/apps/apps/appspec.js +104 -11
- package/lib/apps/apps/threads.d.ts +20 -0
- package/lib/apps/apps/threads.js +31 -0
- package/lib/apps/plugins/canvas-blocks/index.d.ts +7 -9
- package/lib/apps/plugins/canvas-blocks/index.js +8 -10
- package/lib/apps/plugins/canvas-parts/index.d.ts +4 -1
- package/lib/apps/plugins/canvas-parts/index.js +7 -3
- package/lib/apps/plugins/chat/ChatView.js +32 -7
- package/lib/chat/ChatFloating.js +12 -4
- package/lib/chat/base/ChatBase.js +31 -5
- package/lib/chat/messages/ChatMessageList.js +1 -1
- package/lib/config/AgentConfiguration.d.ts +5 -1
- package/lib/examples/AgentA2ATeamExample.js +6 -5
- package/lib/examples/ChatExample.js +2 -2
- package/lib/examples/VoiceChatExample.js +9 -3
- package/lib/hooks/useBrowserAgent.d.ts +1 -1
- package/lib/hooks/useBrowserAgent.js +1 -1
- package/lib/protocols/AGUIAdapter.d.ts +11 -0
- package/lib/protocols/AGUIAdapter.js +27 -2
- package/lib/specs/agents/agents.js +169 -169
- package/lib/specs/apps.d.ts +32 -32
- package/lib/specs/apps.js +624 -143
- package/lib/specs/appspecSchema.js +30 -2
- package/lib/specs/cogs.js +3 -3
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/protocols.js +2 -2
- package/lib/specs/sceneSchema.js +2 -0
- package/lib/specs/scenes.js +7 -7
- package/lib/specs/teams/teams.js +11 -11
- package/lib/specs/ui.d.ts +32 -6
- package/lib/specs/ui.js +760 -26
- package/lib/specs/uiRenderers.d.ts +17 -0
- package/lib/specs/uiRenderers.js +40 -0
- package/lib/specs/voices.d.ts +2 -0
- package/lib/specs/voices.js +5 -0
- package/lib/types/agentspecs.d.ts +63 -12
- package/lib/types/chat.d.ts +6 -0
- package/lib/types/protocol.d.ts +17 -0
- package/lib/utils/utils.js +8 -1
- package/lib/voice/ReadAloud.d.ts +35 -0
- package/lib/voice/ReadAloud.js +29 -0
- package/lib/voice/index.d.ts +7 -2
- package/lib/voice/index.js +1 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_agents.py +60 -11
- package/scripts/codegen/generate_ui.py +68 -22
- package/scripts/codegen/generate_ui_renderers.py +176 -0
- package/scripts/codegen/generate_voices.py +5 -1
- package/dist-embed/chunks/ChatView-nnI1-MOR.js +0 -32
|
@@ -36,7 +36,7 @@ import { askedOf } from '../../apps/apps/records';
|
|
|
36
36
|
import { extractChatMessagesFromFullContext } from './fullContextMessages';
|
|
37
37
|
import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/messages';
|
|
38
38
|
import { AI_MODEL_CATALOGUE, DEFAULT_MODEL, isChatModel, } from '../../specs/models';
|
|
39
|
-
import { internalQueryClient, isToolCallMessage, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
|
|
39
|
+
import { internalQueryClient, isToolCallMessage, getMessageText, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
|
|
40
40
|
import { useConfig, useSkills, useSkillActions, useContextSnapshot, useSandbox, } from '../../hooks';
|
|
41
41
|
import { useAgentRuntimeWebSocket } from '../../hooks/useAgentRuntimes';
|
|
42
42
|
import { agentRuntimeStore, useAgentRuntimeStore, useAgentRuntimeWsState, } from '../../stores/agentRuntimeStore';
|
|
@@ -50,6 +50,7 @@ import { ChatMessageList, } from '../messages/ChatMessageList';
|
|
|
50
50
|
import { InputPrompt } from '../prompt/InputPrompt';
|
|
51
51
|
import { DeviceHearing } from '../../voice/hearing';
|
|
52
52
|
import { VoiceInput } from '../../voice/VoiceInput';
|
|
53
|
+
import { ReadAloud, readsAloud } from '../../voice/ReadAloud';
|
|
53
54
|
import { isFloatingChatViewMode } from '../viewModes';
|
|
54
55
|
import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
|
|
55
56
|
import { initialModelId, isOffered, offeredModels as offeredModelsFor, } from './modelChoice';
|
|
@@ -416,7 +417,7 @@ function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = t
|
|
|
416
417
|
// Mode selection
|
|
417
418
|
useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady, onLoadingChange, onContextSnapshot, enableStreaming = false,
|
|
418
419
|
// Extended props
|
|
419
|
-
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
|
|
420
|
+
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, onReadAloud, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
|
|
420
421
|
// Tool invocation hooks
|
|
421
422
|
onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
|
|
422
423
|
// Identity/Authorization props
|
|
@@ -1467,7 +1468,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1467
1468
|
useEffect(() => {
|
|
1468
1469
|
onContextSnapshot?.(agentUsage);
|
|
1469
1470
|
}, [agentUsage, onContextSnapshot]);
|
|
1470
|
-
|
|
1471
|
+
// Never on a runtime held to the application's own routes: it answers no
|
|
1472
|
+
// sandbox socket (OwnRoutesOnly).
|
|
1473
|
+
const sandboxStatusQuery = useSandbox(configQueriesEnabled && showHeader && !protocol?.ownRoutesOnly, protocol?.configEndpoint, protocol?.authToken, protocol?.agentId);
|
|
1471
1474
|
// ---- Refs ----
|
|
1472
1475
|
const adapterRef = useRef(null);
|
|
1473
1476
|
const unsubscribeRef = useRef(null);
|
|
@@ -1794,8 +1797,16 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1794
1797
|
const wsBaseUrl = protocol?.configEndpoint
|
|
1795
1798
|
? protocol.configEndpoint.replace(/\/api\/v1\/(config|configure)\/?$/, '')
|
|
1796
1799
|
: (protocol?.endpoint?.replace(/\/api\/v1\/.*$/, '') ?? '');
|
|
1800
|
+
/*
|
|
1801
|
+
* Not on a runtime held to the application's own routes (OwnRoutesOnly): it
|
|
1802
|
+
* answers no monitoring stream, and an application's session asks its
|
|
1803
|
+
* approvals in the conversation, over its ui socket.
|
|
1804
|
+
*/
|
|
1797
1805
|
useAgentRuntimeWebSocket({
|
|
1798
|
-
enabled: !!protocol &&
|
|
1806
|
+
enabled: !!protocol &&
|
|
1807
|
+
!!wsBaseUrl &&
|
|
1808
|
+
!isAgentNodeTunnelAgUi &&
|
|
1809
|
+
!protocol.ownRoutesOnly,
|
|
1799
1810
|
baseUrl: wsBaseUrl,
|
|
1800
1811
|
authToken: protocol?.authToken,
|
|
1801
1812
|
agentId: protocol?.agentId,
|
|
@@ -3739,6 +3750,21 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3739
3750
|
...displayItems,
|
|
3740
3751
|
]
|
|
3741
3752
|
: displayItems;
|
|
3753
|
+
/*
|
|
3754
|
+
* *Read aloud* under each answer the agent has finished (VOICE.md VO-21),
|
|
3755
|
+
* when the host reads answers aloud on request.
|
|
3756
|
+
*/
|
|
3757
|
+
const writingId = isLoading || isStreaming
|
|
3758
|
+
? [...welcomedItems]
|
|
3759
|
+
.reverse()
|
|
3760
|
+
.find((item) => !isToolCallMessage(item) && item.role === 'assistant')?.id
|
|
3761
|
+
: undefined;
|
|
3762
|
+
const readAloudFooter = onReadAloud
|
|
3763
|
+
? (message) => {
|
|
3764
|
+
const text = getMessageText(message);
|
|
3765
|
+
return readsAloud(message, text, writingId) ? (_jsx(ReadAloud, { onRead: () => onReadAloud(text) })) : null;
|
|
3766
|
+
}
|
|
3767
|
+
: undefined;
|
|
3742
3768
|
const messagesContent = children ? (children) : (_jsxs(Box, { display: "flex", flexDirection: "column", minHeight: 0,
|
|
3743
3769
|
/*
|
|
3744
3770
|
A reading column, not a full-bleed sheet.
|
|
@@ -3752,7 +3778,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3752
3778
|
// Tool calls are drawn by the page's Reactor plugins; a named
|
|
3753
3779
|
// notebook's cell surfaces stand in for the default row of its tools,
|
|
3754
3780
|
// so every chat with a headless notebook gets the same transcript.
|
|
3755
|
-
notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
|
|
3781
|
+
notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, renderMessageFooter: readAloudFooter, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
|
|
3756
3782
|
// The microphone, in the composer's footer (VO-10).
|
|
3757
3783
|
const voiceControl = hearing && voice ? (_jsx(VoiceInput, { hearing: hearing, language: voice.language, consentKey: voice.consentKey ?? 'chat', disabled: disabled || launching, speaking: voiceSpeaking, onStopSpeaking: onStopSpeaking, onListen: onStopSpeaking, onTranscript: (transcript) => {
|
|
3758
3784
|
if (voice.sendWhatISay) {
|
|
@@ -611,7 +611,7 @@ export function ChatMessageList({ displayItems, actOn, upload, isLoading, isStre
|
|
|
611
611
|
whiteSpace: 'pre-wrap',
|
|
612
612
|
wordBreak: 'break-word',
|
|
613
613
|
}, children: [message.metadata?.input === 'voice' ? (_jsx(Box, { as: "span", "data-spoken": "", title: "Said aloud", "aria-label": "Said aloud:", display: "inline-flex", verticalAlign: "text-bottom", mr: 1, opacity: 0.85, children: _jsx(MicrophoneIcon, { size: 14, "aria-hidden": "true" }) })) : null, text] }));
|
|
614
|
-
})()) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(ChatMarkdown, { density: density, text: getMessageText(message) || (isStreaming ? '...' : '') }))] }), message.displays?.length ? (_jsx(Box, { "data-chat-displays": "", display: "flex", flexDirection: "column", gap: 2, mt: 2, width: "100%", children: message.displays.map(display => platform ? (_jsx(ReactorDisplay, { display: display, act: actOn?.(display), upload: upload }, display.id)) : (_jsx(DisplayText, { display: display }, display.id))) })) : null, renderMessageFooter ? (_jsx(Box, { display: "flex", gap: 2, alignItems: "center", mt: 1, flexWrap: "wrap", flexDirection: isUser ? 'row-reverse' : 'row', children: renderMessageFooter(message) })) : null] })] }) }, message.id));
|
|
614
|
+
})()) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(ChatMarkdown, { density: density, text: getMessageText(message) || (isStreaming ? '...' : '') }))] }), message.displays?.length ? (_jsx(Box, { "data-chat-displays": "", display: "flex", flexDirection: "column", gap: 2, mt: 2, width: "100%", children: message.displays.map(display => platform ? (_jsx(ReactorDisplay, { display: display, act: actOn?.(display), upload: upload }, display.id)) : (_jsx(DisplayText, { display: display }, display.id))) })) : null, renderMessageFooter?.(message) ? (_jsx(Box, { display: "flex", gap: 2, alignItems: "center", mt: 1, flexWrap: "wrap", flexDirection: isUser ? 'row-reverse' : 'row', children: renderMessageFooter(message) })) : null] })] }) }, message.id));
|
|
615
615
|
};
|
|
616
616
|
return (_jsxs(_Fragment, { children: [displayItems.map((item, index) => {
|
|
617
617
|
const itemUI = renderItem(item, index);
|
|
@@ -29,7 +29,11 @@ export interface LibraryAgentspec {
|
|
|
29
29
|
welcomeDocument?: string | null;
|
|
30
30
|
sandboxVariant?: string | null;
|
|
31
31
|
protocol?: string | null;
|
|
32
|
-
|
|
32
|
+
/** Its UI plugins, each with the renderer it is exposed through (AGENTIC_UI R8). */
|
|
33
|
+
ui?: Array<{
|
|
34
|
+
plugin: string;
|
|
35
|
+
renderer?: string;
|
|
36
|
+
}> | null;
|
|
33
37
|
trigger?: Record<string, unknown> | null;
|
|
34
38
|
modelConfig?: Record<string, unknown> | null;
|
|
35
39
|
mcpServerTools?: Array<Record<string, unknown>> | null;
|
|
@@ -11,12 +11,13 @@ import { useBrowserInference } from '../hooks/useBrowserInference';
|
|
|
11
11
|
import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
|
|
12
12
|
import { connectA2APeer } from '../runtimes/browser/a2aPeer';
|
|
13
13
|
import { A2ATeamGraph, SceneView, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
|
|
14
|
-
import {
|
|
14
|
+
import { APP_CATALOGUE } from '../specs/apps';
|
|
15
15
|
import { AgentInspector, classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
16
|
+
import { TEAM_SPECS } from '../specs/teams/teams';
|
|
17
|
+
// By id, never by the version-named constants: a version bump touches no caller.
|
|
18
|
+
const TEAM = TEAM_SPECS['sales-and-accounting'];
|
|
19
|
+
const SALES = APP_CATALOGUE['sales'];
|
|
20
|
+
const ACCOUNTING = APP_CATALOGUE['accounting'];
|
|
20
21
|
/** What Accounting reaches, from its Appspec: Odoo, through the odoo-accounting MCP server. */
|
|
21
22
|
const ACCOUNTING_CONNECTIONS = teamConnectionsOf(ACCOUNTING);
|
|
22
23
|
/** The members of the team, as the team spec says them. */
|
|
@@ -30,13 +30,13 @@ import { uniqueAgentId } from './utils/agentId';
|
|
|
30
30
|
import { useExampleAgentRuntime } from './hooks/useExampleAgentRuntime';
|
|
31
31
|
import { BROWSER_SIGN_IN_REASON, useExampleAgentProtocol, } from './hooks/useExampleAgentProtocol';
|
|
32
32
|
import { runsInBrowser } from '../runtimes/variants';
|
|
33
|
-
import {
|
|
33
|
+
import { AGENTSPECS } from '../specs/agents/agents';
|
|
34
34
|
import { ErrorView } from './components';
|
|
35
35
|
import { Chat } from '../chat';
|
|
36
36
|
setupPrimerPortals();
|
|
37
37
|
const AGENTSPEC_ID = 'example-simple';
|
|
38
38
|
const AGENT_NAME = 'simple';
|
|
39
|
-
const SPEC =
|
|
39
|
+
const SPEC = AGENTSPECS[AGENTSPEC_ID];
|
|
40
40
|
const AgentRuntimeChatExample = () => {
|
|
41
41
|
const agentName = useMemo(() => uniqueAgentId(AGENT_NAME), []);
|
|
42
42
|
const { agentId, baseUrl, error, status, isReady, variant } = useExampleAgentRuntime({
|
|
@@ -45,21 +45,27 @@ const LANGUAGES = [
|
|
|
45
45
|
{ tag: 'en-US', label: 'English' },
|
|
46
46
|
{ tag: 'fr-FR', label: 'Français' },
|
|
47
47
|
];
|
|
48
|
+
/** When answers are heard (VO-21): on request is the Appspec's default. */
|
|
49
|
+
const OUTPUTS = [
|
|
50
|
+
{ output: 'on_request', label: 'Read aloud on request' },
|
|
51
|
+
{ output: 'always', label: 'Always read aloud' },
|
|
52
|
+
];
|
|
48
53
|
const VoiceChatExample = () => {
|
|
49
54
|
const { token } = useSimpleAuthStore();
|
|
50
55
|
const [language, setLanguage] = useState('en-US');
|
|
56
|
+
const [output, setOutput] = useState('on_request');
|
|
51
57
|
const voiceSpec = voiceFor(language) ?? VOICE_CATALOGUE['kokoro-af-heart'];
|
|
52
58
|
const voice = useMemo(() => ({
|
|
53
59
|
language,
|
|
54
60
|
input: 'push_to_talk',
|
|
55
|
-
output
|
|
61
|
+
output,
|
|
56
62
|
voice: voiceSpec.id,
|
|
57
63
|
modelsUrl: MODELS,
|
|
58
64
|
engines: ENGINES,
|
|
59
65
|
speechUrl: SPEECH,
|
|
60
66
|
token: token || undefined,
|
|
61
67
|
consentKey: 'voice-chat-example',
|
|
62
|
-
}), [language, voiceSpec.id, token]);
|
|
63
|
-
return (_jsx(ThemedProvider, { children: _jsxs(Box, { minHeight: "100vh", bg: "canvas.default", p: 4, children: [_jsxs(Box, { maxWidth: 720, mx: "auto", children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Voice chat" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: ["Talk to the assistant instead of typing: hold the microphone in its composer, or ", _jsx("kbd", { children: "Ctrl" }), "+", _jsx("kbd", { children: "Space" }), ", speak, and let go. What you say is heard in this page and never leaves it; the words go in the message box for you to send. Its answers are read aloud
|
|
68
|
+
}), [language, output, voiceSpec.id, token]);
|
|
69
|
+
return (_jsx(ThemedProvider, { children: _jsxs(Box, { minHeight: "100vh", bg: "canvas.default", p: 4, children: [_jsxs(Box, { maxWidth: 720, mx: "auto", children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Voice chat" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: ["Talk to the assistant instead of typing: hold the microphone in its composer, or ", _jsx("kbd", { children: "Ctrl" }), "+", _jsx("kbd", { children: "Space" }), ", speak, and let go. What you say is heard in this page and never leaves it; the words go in the message box for you to send. Its answers are read aloud by Datalayer\u2019s speech service, and not kept: on request, with", ' ', _jsx("em", { children: "Read aloud" }), " under an answer, or always, as they are written.", ' ', _jsx("kbd", { children: "Esc" }), " stops its voice."] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, mb: 3, flexWrap: "wrap", children: [_jsx(SegmentedControl, { "aria-label": "Language", children: LANGUAGES.map(option => (_jsx(SegmentedControl.Button, { selected: language === option.tag, onClick: () => setLanguage(option.tag), "data-voice-language": option.tag, children: option.label }, option.tag))) }), _jsx(SegmentedControl, { "aria-label": "When answers are heard", children: OUTPUTS.map(option => (_jsx(SegmentedControl.Button, { selected: output === option.output, onClick: () => setOutput(option.output), "data-voice-output": option.output, children: option.label }, option.output))) }), _jsxs(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: ["Voice: ", voiceSpec.name] })] }), voiceSpec.attribution && (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: voiceSpec.attribution })), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted' }, children: ["Models from ", MODELS, ", speech from ", SPEECH, ", the agent at ", SERVER, ". See", ' ', _jsx(Link, { href: "https://agent-runtimes.datalayer.tech/chat/voice", children: "Voice" }), ' ', "for how to run each."] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: "paperclip", protocol: "vercel-ai", endpoint: `${SERVER}/api/v1/vercel-ai/assistant`, title: "Assistant", description: "Hello! Hold the microphone and ask me anything.", position: "bottom-right", useStore: false, voice: voice })] }) }));
|
|
64
70
|
};
|
|
65
71
|
export default VoiceChatExample;
|
|
@@ -14,7 +14,7 @@ export type UseBrowserAgentOptions = CreateBrowserAgentOptions & {
|
|
|
14
14
|
* ```tsx
|
|
15
15
|
* const frontendTools = useNotebookTools(notebookId);
|
|
16
16
|
* const { messages, sendMessage, status } = useBrowserAgent({
|
|
17
|
-
* spec:
|
|
17
|
+
* spec: AGENTSPECS['jupyter-notebook-compactor'],
|
|
18
18
|
* frontendTools,
|
|
19
19
|
* inference: { inferenceUrl, token },
|
|
20
20
|
* });
|
|
@@ -35,7 +35,7 @@ import { createBrowserAgent, } from '../runtimes/browser';
|
|
|
35
35
|
* ```tsx
|
|
36
36
|
* const frontendTools = useNotebookTools(notebookId);
|
|
37
37
|
* const { messages, sendMessage, status } = useBrowserAgent({
|
|
38
|
-
* spec:
|
|
38
|
+
* spec: AGENTSPECS['jupyter-notebook-compactor'],
|
|
39
39
|
* frontendTools,
|
|
40
40
|
* inference: { inferenceUrl, token },
|
|
41
41
|
* });
|
|
@@ -15,6 +15,11 @@ export declare const RUN_CUT_SHORT = "The run stopped before it finished, withou
|
|
|
15
15
|
export interface AGUIAdapterConfig extends ProtocolAdapterConfig {
|
|
16
16
|
/** Runtime URL (e.g., /api/copilotkit) */
|
|
17
17
|
runtimeUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* An application's sessions (`…/api/v1/apps/sessions`): Stop is the
|
|
20
|
+
* thread's session's (`ProtocolConfig.appSessions`).
|
|
21
|
+
*/
|
|
22
|
+
appSessions?: string;
|
|
18
23
|
}
|
|
19
24
|
/**
|
|
20
25
|
* AG-UI protocol adapter
|
|
@@ -63,6 +68,12 @@ export declare class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
63
68
|
* Terminate a running agent thread on the backend
|
|
64
69
|
*/
|
|
65
70
|
terminateAgent(threadId?: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Stop an application's session (`POST {sessions}/{uid}/stop`): what runs
|
|
73
|
+
* is cancelled and it waits to be resumed. Its thread is the session; with
|
|
74
|
+
* none, nothing was sent and there is nothing to stop.
|
|
75
|
+
*/
|
|
76
|
+
private stopSession;
|
|
66
77
|
/**
|
|
67
78
|
* Send a message through AG-UI protocol
|
|
68
79
|
* @param message - The new message to send
|
|
@@ -86,8 +86,10 @@ export class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
86
86
|
disconnect() {
|
|
87
87
|
// Abort every ongoing fetch request, not merely the newest.
|
|
88
88
|
this.stopGeneration();
|
|
89
|
-
// Send terminate request to backend if we have a thread ID
|
|
90
|
-
|
|
89
|
+
// Send terminate request to backend if we have a thread ID. Never an
|
|
90
|
+
// application's session: a page going away, or the chat rebuilt with a
|
|
91
|
+
// fresh token, is not the person's Stop — it waits for them to come back.
|
|
92
|
+
if (this.currentThreadId && !this.aguiConfig.appSessions) {
|
|
91
93
|
this.terminateAgent(this.currentThreadId).catch(err => {
|
|
92
94
|
console.warn('[AGUIAdapter] Failed to terminate agent:', err);
|
|
93
95
|
});
|
|
@@ -98,6 +100,11 @@ export class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
98
100
|
* Terminate a running agent thread on the backend
|
|
99
101
|
*/
|
|
100
102
|
async terminateAgent(threadId) {
|
|
103
|
+
const sessions = this.aguiConfig.appSessions;
|
|
104
|
+
if (sessions) {
|
|
105
|
+
await this.stopSession(sessions, threadId || this.currentThreadId);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
101
108
|
try {
|
|
102
109
|
// Derive the terminate endpoint from the base URL
|
|
103
110
|
// e.g., http://localhost:8765/api/v1/ag-ui/default -> http://localhost:8765/api/v1/ag-ui/terminate
|
|
@@ -121,6 +128,24 @@ export class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
121
128
|
// Ignore errors - agent may have already completed
|
|
122
129
|
}
|
|
123
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Stop an application's session (`POST {sessions}/{uid}/stop`): what runs
|
|
133
|
+
* is cancelled and it waits to be resumed. Its thread is the session; with
|
|
134
|
+
* none, nothing was sent and there is nothing to stop.
|
|
135
|
+
*/
|
|
136
|
+
async stopSession(sessions, threadId) {
|
|
137
|
+
if (!threadId) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const url = `${sessions.replace(/\/+$/, '')}/${encodeURIComponent(threadId)}/stop`;
|
|
141
|
+
const response = await this.fetchOf()(url, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: this.buildHeaders(),
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`The session ${threadId} could not be stopped (${response.status}).`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
124
149
|
/**
|
|
125
150
|
* Send a message through AG-UI protocol
|
|
126
151
|
* @param message - The new message to send
|