@datalayer/agent-runtimes 1.3.84 → 1.3.86

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 (162) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +14 -11
  17. package/lib/chat/display/EmptyState.js +3 -1
  18. package/lib/chat/index.d.ts +2 -0
  19. package/lib/chat/index.js +3 -0
  20. package/lib/chat/messages/ChatMessages.js +3 -1
  21. package/lib/chat/messages/TurnFooter.js +3 -1
  22. package/lib/chat/prompt/InputPrompt.js +3 -1
  23. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  24. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  25. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  26. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  27. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  28. package/lib/chat/words.d.ts +102 -0
  29. package/lib/chat/words.js +460 -0
  30. package/lib/components/a2ui/custom/index.d.ts +39 -0
  31. package/lib/components/a2ui/custom/index.js +95 -0
  32. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  33. package/lib/components/a2ui/custom/sandbox.js +188 -0
  34. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  35. package/lib/components/a2ui/datalayer/index.js +1 -1
  36. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  37. package/lib/components/a2ui/datalayer/schema.js +12 -1
  38. package/lib/components/a2ui/index.d.ts +1 -0
  39. package/lib/components/a2ui/index.js +1 -0
  40. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  41. package/lib/components/a2ui/visibleWhen.js +24 -3
  42. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  43. package/lib/components/inspector/AgentInspector.js +27 -215
  44. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  45. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  46. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  47. package/lib/components/inspector/a2aSpans.js +373 -0
  48. package/lib/components/inspector/agentSpans.d.ts +120 -0
  49. package/lib/components/inspector/agentSpans.js +253 -0
  50. package/lib/components/inspector/chatSpans.d.ts +36 -0
  51. package/lib/components/inspector/chatSpans.js +50 -0
  52. package/lib/components/inspector/classify.d.ts +11 -8
  53. package/lib/components/inspector/classify.js +23 -24
  54. package/lib/components/inspector/index.d.ts +7 -7
  55. package/lib/components/inspector/index.js +7 -7
  56. package/lib/components/teams/A2ATeamGraph.d.ts +117 -22
  57. package/lib/components/teams/A2ATeamGraph.js +217 -97
  58. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  59. package/lib/components/teams/SceneTranscript.js +54 -0
  60. package/lib/components/teams/SceneView.d.ts +41 -0
  61. package/lib/components/teams/SceneView.js +28 -0
  62. package/lib/components/teams/index.d.ts +4 -0
  63. package/lib/components/teams/index.js +4 -0
  64. package/lib/components/teams/sceneStage.d.ts +120 -0
  65. package/lib/components/teams/sceneStage.js +170 -0
  66. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  67. package/lib/components/teams/sceneTranscript.js +294 -0
  68. package/lib/components/teams/teamConnections.d.ts +2 -2
  69. package/lib/components/teams/teamConnections.js +3 -3
  70. package/lib/components/teams/useA2ATeam.d.ts +94 -22
  71. package/lib/components/teams/useA2ATeam.js +356 -140
  72. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  73. package/lib/examples/AgentA2ATeamExample.js +70 -60
  74. package/lib/examples/AssistantExample.js +2 -2
  75. package/lib/examples/ScenesExample.d.ts +26 -0
  76. package/lib/examples/ScenesExample.js +241 -0
  77. package/lib/examples/example-selector.js +1 -0
  78. package/lib/examples/exampleGroups.js +3 -0
  79. package/lib/examples/index.d.ts +1 -0
  80. package/lib/examples/index.js +1 -0
  81. package/lib/examples/main.js +4 -0
  82. package/lib/loop/apps/AppComposer.d.ts +2 -1
  83. package/lib/loop/apps/AppComposer.js +6 -3
  84. package/lib/loop/apps/AppRenderer.d.ts +2 -1
  85. package/lib/loop/apps/AppRenderer.js +34 -25
  86. package/lib/loop/apps/appspec.d.ts +11 -1
  87. package/lib/loop/apps/appspec.js +115 -1
  88. package/lib/loop/apps/checks.d.ts +8 -0
  89. package/lib/loop/apps/checks.js +106 -4
  90. package/lib/loop/apps/customComponents.d.ts +41 -0
  91. package/lib/loop/apps/customComponents.js +260 -0
  92. package/lib/loop/apps/hostTools.d.ts +8 -1
  93. package/lib/loop/apps/hostTools.js +9 -0
  94. package/lib/loop/apps/index.d.ts +1 -0
  95. package/lib/loop/apps/index.js +1 -0
  96. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  97. package/lib/loop/core/a2uiComponents.js +15 -0
  98. package/lib/loop/core/index.d.ts +1 -0
  99. package/lib/loop/core/index.js +3 -0
  100. package/lib/loop/embed/AppEmbed.d.ts +11 -3
  101. package/lib/loop/embed/AppEmbed.js +42 -16
  102. package/lib/loop/embed/element.d.ts +5 -0
  103. package/lib/loop/embed/element.js +61 -5
  104. package/lib/loop/embed/embedConfig.d.ts +8 -0
  105. package/lib/loop/embed/embedConfig.js +18 -0
  106. package/lib/loop/embed/hostBridge.d.ts +28 -2
  107. package/lib/loop/embed/hostBridge.js +22 -2
  108. package/lib/loop/index.d.ts +4 -3
  109. package/lib/loop/index.js +4 -3
  110. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  111. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  112. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  113. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  114. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  115. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  116. package/lib/loop/plugins/app-components/index.js +56 -0
  117. package/lib/loop/plugins/app-elements/AppElements.js +4 -1
  118. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  119. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  120. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  121. package/lib/loop/plugins/app-page/appPageModel.js +120 -4
  122. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  123. package/lib/loop/plugins/app-page/index.js +1 -0
  124. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  125. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  126. package/lib/loop/plugins/assistant/index.js +8 -6
  127. package/lib/loop/plugins/chat/ChatView.js +6 -4
  128. package/lib/loop/plugins/chat/index.d.ts +1 -1
  129. package/lib/loop/plugins/chat/index.js +2 -1
  130. package/lib/models/PageTag.d.ts +1 -1
  131. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  132. package/lib/specs/actions.js +78 -1
  133. package/lib/specs/apps.d.ts +5 -0
  134. package/lib/specs/apps.js +1384 -231
  135. package/lib/specs/appspecSchema.js +191 -1
  136. package/lib/specs/index.d.ts +1 -0
  137. package/lib/specs/index.js +1 -0
  138. package/lib/specs/mcpServers.js +8 -2
  139. package/lib/specs/scenes.d.ts +21 -0
  140. package/lib/specs/scenes.js +1191 -0
  141. package/lib/specs/teams/teams.d.ts +3 -0
  142. package/lib/specs/teams/teams.js +249 -0
  143. package/lib/types/agentspecs.d.ts +70 -0
  144. package/lib/types/chat.d.ts +6 -5
  145. package/lib/types/index.d.ts +1 -0
  146. package/lib/types/index.js +1 -0
  147. package/lib/types/scenes.d.ts +207 -0
  148. package/lib/types/scenes.js +5 -0
  149. package/lib/types/teams.d.ts +13 -2
  150. package/package.json +4 -4
  151. package/scripts/codegen/generate_agents.py +1 -0
  152. package/scripts/codegen/generate_apps.py +4 -2
  153. package/scripts/codegen/generate_scenes.py +301 -0
  154. package/scripts/codegen/generate_teams.py +15 -2
  155. package/lib/components/inspector/JsonTree.d.ts +0 -24
  156. package/lib/components/inspector/JsonTree.js +0 -89
  157. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  158. package/lib/components/inspector/a2aInspect.js +0 -478
  159. package/lib/components/inspector/agentInspector.d.ts +0 -179
  160. package/lib/components/inspector/agentInspector.js +0 -267
  161. package/lib/components/inspector/chatInspect.d.ts +0 -38
  162. package/lib/components/inspector/chatInspect.js +0 -65
@@ -23,7 +23,7 @@ import { type AssistantCharacter } from './assistant/characters';
23
23
  import type { AssistantCharacterData } from './assistant/formats/types';
24
24
  import { type AssistantSaying } from './assistant/state';
25
25
  import { type PresenceState } from './presence/presenceStatus';
26
- import type { AgentInspectorSink } from '../components/inspector/agentInspector';
26
+ import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
27
27
  import type { AssistantMenuItem } from './assistant/AssistantContextMenu';
28
28
  import type { AssistantAbout } from './assistant/AssistantStage';
29
29
  import { type AssistantSandbox } from './assistant/assistantDetails';
@@ -177,10 +177,11 @@ export interface ChatFloatingProps extends ChatCommonProps {
177
177
  */
178
178
  conversation?: FloatingConversation;
179
179
  /**
180
- * The Agent Inspector's sink: the chat records its agent's turns and tool
181
- * calls there, and the assistant's menu offers *Inspect the agent…*.
180
+ * The Agent Inspector's tracer: the chat records its agent's turns and
181
+ * tool calls there as spans, and the assistant's menu offers *Inspect the
182
+ * agent…*.
182
183
  */
183
- inspector?: AgentInspectorSink | null;
184
+ inspector?: OtelLiveTracer | null;
184
185
  /** The balloon's display, changed from the assistant's menu. */
185
186
  onBalloonDisplayChange?: (display: BalloonDisplay) => void;
186
187
  /** Opens the host's character picker, from the assistant's menu. */
@@ -40,12 +40,13 @@ import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/Ball
40
40
  import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
41
41
  import { SpeechBalloon } from './assistant/SpeechBalloon';
42
42
  import { DEFAULT_ASSISTANT_CHARACTER, } from './assistant/characters';
43
- import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, ASSISTANT_WORDS, } from './assistant/state';
43
+ import { assistantStateOf, latestSaying, newestIsAnswer, keepAway, keptAway, } from './assistant/state';
44
44
  import { presenceState, presenceToolOf, } from './presence/presenceStatus';
45
45
  import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
46
46
  import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
47
47
  import { assistantSandboxOf, } from './assistant/assistantDetails';
48
48
  import { useAgentRuntimeCodemodeStatus } from '../stores/agentRuntimeStore';
49
+ import { useChatWords } from './ChatLanguage';
49
50
  /**
50
51
  * Hook to detect mobile viewport
51
52
  */
@@ -73,6 +74,7 @@ const clamp = (value, max) => Math.max(8, Math.min(value, Math.max(8, max)));
73
74
  * A floating chat window built on ChatBase
74
75
  */
75
76
  export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, themeOverrides, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, assistantCharacter = DEFAULT_ASSISTANT_CHARACTER, balloonDisplay: balloonDisplayGiven = DEFAULT_BALLOON_DISPLAY, expandTarget, conversation, decisions, voice, inspector, onBalloonDisplayChange, onChangeCharacter, about, sandbox, contextMenu, }) {
77
+ const chatText = useChatWords();
76
78
  // The chat's own send, once it can: what a suggestion in the balloon sends.
77
79
  const chatControls = useRef(null);
78
80
  const [suggestionPrompt, setSuggestionPrompt] = useState();
@@ -280,7 +282,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
280
282
  // The words as written (markdown, drawn by the chat's markdown), or cut
281
283
  // to fit with the whole kept for *more*.
282
284
  const currentWords = atWork && !answering
283
- ? 'Thinking…'
285
+ ? chatText.thinking
284
286
  : (saying?.more ? saying.text : saying?.markdown) || undefined;
285
287
  const currentWhole = atWork && !answering
286
288
  ? undefined
@@ -293,7 +295,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
293
295
  : undefined;
294
296
  const withHistory = (said) => listedHistory ? { ...said, history: listedHistory } : said;
295
297
  const assistantBalloon = assistantState === 'paused'
296
- ? { text: ASSISTANT_WORDS.paused }
298
+ ? { text: chatText.paused }
297
299
  : speakerState.sentence
298
300
  ? // Heard: the sentence being said is the one in the balloon (VO-26).
299
301
  { text: speakerState.sentence }
@@ -301,14 +303,14 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
301
303
  ? { text: speakerState.refused }
302
304
  : balloonApproval
303
305
  ? withHistory({
304
- text: ASSISTANT_WORDS.approval,
306
+ text: chatText.waitingForYou,
305
307
  approval: balloonApproval,
306
308
  })
307
309
  : assistantState === 'waiting'
308
- ? { text: ASSISTANT_WORDS.waiting }
310
+ ? { text: chatText.waitingOpen }
309
311
  : toolSaid
310
312
  ? withHistory({
311
- text: toolLineText(toolSaid),
313
+ text: toolLineText(toolSaid, chatText),
312
314
  tool: toolSaid,
313
315
  busy: atWork,
314
316
  })
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The language the chat speaks in (LOOP P-26): the one its host says — the
3
+ * `language` of `AppRenderer` and of the embed — else the browser's.
4
+ *
5
+ * @module chat/ChatLanguage
6
+ */
7
+ import { type JSX, type ReactNode } from 'react';
8
+ import { type ChatWords } from './words';
9
+ /** The chat in a language its host says; the browser's when it says none. */
10
+ export declare function ChatLanguage({ language, children, }: {
11
+ language?: string;
12
+ children?: ReactNode;
13
+ }): JSX.Element;
14
+ /** The language the chat speaks: the host's, else the browser's, else English. */
15
+ export declare function useChatLanguage(): string;
16
+ /** The chat's own words, in the language it speaks. */
17
+ export declare function useChatWords(): ChatWords;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * The language the chat speaks in (LOOP P-26): the one its host says — the
8
+ * `language` of `AppRenderer` and of the embed — else the browser's.
9
+ *
10
+ * @module chat/ChatLanguage
11
+ */
12
+ import { createContext, useContext, useMemo, } from 'react';
13
+ import { preferredLanguages } from '../loop/apps/language';
14
+ import { CHAT_WORDS, chatLanguage } from './words';
15
+ /** The language the host says, as BCP 47 tags it; unsaid, the browser's. */
16
+ const ChatLanguageContext = createContext(undefined);
17
+ /** The chat in a language its host says; the browser's when it says none. */
18
+ export function ChatLanguage({ language, children, }) {
19
+ return (_jsx(ChatLanguageContext.Provider, { value: language || undefined, children: children }));
20
+ }
21
+ /** The language the chat speaks: the host's, else the browser's, else English. */
22
+ export function useChatLanguage() {
23
+ const said = useContext(ChatLanguageContext);
24
+ return useMemo(() => chatLanguage(said ? [said] : preferredLanguages()), [said]);
25
+ }
26
+ /** The chat's own words, in the language it speaks. */
27
+ export function useChatWords() {
28
+ return CHAT_WORDS[useChatLanguage()];
29
+ }
@@ -30,7 +30,7 @@ import type { JSX, ReactNode, RefObject } from 'react';
30
30
  import { type AssistantCharacter } from './characters';
31
31
  import { type BalloonSuggestion } from './SpeechBalloon';
32
32
  import { type AssistantMenuItem } from './AssistantContextMenu';
33
- import type { AgentInspectorSink } from '../../components/inspector/agentInspector';
33
+ import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
34
34
  import type { AssistantSandbox } from './assistantDetails';
35
35
  import { type BalloonExpandTarget, type BalloonVisual } from './BalloonVisual';
36
36
  import type { AssistantCharacterData } from './formats/types';
@@ -220,9 +220,9 @@ export interface AssistantStageProps {
220
220
  */
221
221
  suggestions?: readonly BalloonSuggestion[];
222
222
  onSuggestion?: (suggestion: BalloonSuggestion) => void;
223
- /** The Agent Inspector's sink: its menu offers *Inspect the agent…*. */
224
- inspector?: AgentInspectorSink | null;
225
- /** The agent whose own record the inspector shows, of a shared sink (a team's member). */
223
+ /** The Agent Inspector's tracer: its menu offers *Inspect the agent…*. */
224
+ inspector?: OtelLiveTracer | null;
225
+ /** The agent whose own spans the inspector shows, of a shared tracer (a team's member). */
226
226
  inspectAgent?: string;
227
227
  /**
228
228
  * What its menu calls the conversation's entry, which does `onToggle`;
@@ -12,6 +12,7 @@ import { BalloonExpandContext, ExpandedVisual, focusExpanded, } from './BalloonV
12
12
  import { SpriteCharacter } from './SpriteCharacter';
13
13
  import { conversationCount, } from './toolLine';
14
14
  import { ASSISTANT_OBSTACLES, POINTER_CALM_MS, mouthOpening, boxesMeet, pointerNear, } from './state';
15
+ import { useChatWords } from '../ChatLanguage';
15
16
  /** The motions, by the state the stage is in. */
16
17
  const MOTIONS = {
17
18
  '@keyframes assistantFloat': {
@@ -362,6 +363,7 @@ export function useMouth(stageRef, mouthLevel, speaking) {
362
363
  }, [stageRef, mouthLevel, speaking]);
363
364
  }
364
365
  export function AssistantStage({ character, state, size = 88, place, stageRef, onDragStart, open, onToggle, onCharacterClick, onCharacterKeyDown, balloon, insist = false, onDismiss, ownRef, decide, mouthLevel, stayPut = false, balloonDisplay = 'history', expandTarget, expandOnArrival = false, suggestions, onSuggestion, inspector, inspectAgent, conversationLabel, onBalloonDisplayChange, onStop, onNewChat, onClear, onChangeCharacter, speech, onResetPosition, about, sandbox, contextMenu, agentName, label, }) {
366
+ const chatText = useChatWords();
365
367
  // A shipped one by id, a drawing contributed by a plugin (T-24), or a
366
368
  // character read from a file (T-26).
367
369
  const shipped = typeof character === 'string'
@@ -456,7 +458,7 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
456
458
  const historyVisual = balloonDisplay !== 'current' && history && history.length > 0
457
459
  ? {
458
460
  id: 'balloon-history',
459
- title: conversationHeaderText(conversationCount(history)),
461
+ title: conversationHeaderText(conversationCount(history), chatText),
460
462
  shrink: true,
461
463
  render: () => (_jsx(BalloonHistoryLarge, { history: history, tool: balloon?.tool, attachment: balloon?.attachment })),
462
464
  }
@@ -643,7 +645,7 @@ export function AssistantStage({ character, state, size = 88, place, stageRef, o
643
645
  bg: 'canvas.default',
644
646
  borderRadius: '50%',
645
647
  boxShadow: 'shadow.small',
646
- } }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: AWAY_CHOICES.map(choice => (_jsx(ActionList.Item, { "data-assistant-away": choice.away, onSelect: () => onDismiss(choice.away), children: choice.label }, choice.away))) }) })] }) })), _jsx(AssistantContextMenu, { at: menuAt, items: menuItems, onClose: closeMenu, returnFocusRef: characterRef, label: `${name}\u2019s menu` }), inspecting && (_jsx(Suspense, { fallback: null, children: _jsx(AgentInspectorDialog, { sink: inspector ?? null, agent: inspectAgent, title: `${about?.name ?? name} \u00b7 Agent Inspector`, onClose: () => {
648
+ } }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: AWAY_CHOICES.map(choice => (_jsx(ActionList.Item, { "data-assistant-away": choice.away, onSelect: () => onDismiss(choice.away), children: choice.label }, choice.away))) }) })] }) })), _jsx(AssistantContextMenu, { at: menuAt, items: menuItems, onClose: closeMenu, returnFocusRef: characterRef, label: `${name}\u2019s menu` }), inspecting && (_jsx(Suspense, { fallback: null, children: _jsx(AgentInspectorDialog, { tracer: inspector ?? null, agent: inspectAgent, title: `${about?.name ?? name} \u00b7 Agent Inspector`, onClose: () => {
647
649
  setInspecting(false);
648
650
  characterRef.current?.focus();
649
651
  } }) })), detailing === 'agent' && about && (_jsx(Suspense, { fallback: null, children: _jsx(AgentDetailsDialog, { about: about, name: name, onClose: () => {
@@ -10,6 +10,7 @@ import type { JSX, ReactNode } from 'react';
10
10
  import { type ChatDensity } from '../messages/ChatMarkdown';
11
11
  import type { DisplayItem } from '../../types/chat';
12
12
  import { type BalloonToolLine } from './toolLine';
13
+ import { type ChatWords } from '../words';
13
14
  /**
14
15
  * The conversation's items in the balloon, drawn by the chat's own
15
16
  * components (`ChatMessageList`): markdown, code blocks, tool calls and
@@ -46,7 +47,7 @@ export declare function BalloonNow({ busy }: {
46
47
  busy?: boolean;
47
48
  }): JSX.Element;
48
49
  /** How the `history` balloon names itself: "Conversation · 6". */
49
- export declare function conversationHeaderText(count: number): string;
50
+ export declare function conversationHeaderText(count: number, chatText?: ChatWords): string;
50
51
  /**
51
52
  * The `history` balloon's header: what it holds — the conversation, and how
52
53
  * many messages — over the scrolled history and the composer.
@@ -7,6 +7,8 @@ import { ChatMessageList } from '../messages/ChatMessageList';
7
7
  import { ChatMarkdown } from '../messages/ChatMarkdown';
8
8
  import { agentRuntimeStore } from '../../stores/agentRuntimeStore';
9
9
  import { toolAnnouncement, displayItemOfLine, } from './toolLine';
10
+ import { useChatWords } from '../ChatLanguage';
11
+ import { ENGLISH_CHAT_WORDS } from '../words';
10
12
  /** Out of sight, still heard. */
11
13
  const VISUALLY_HIDDEN = {
12
14
  position: 'absolute',
@@ -64,10 +66,11 @@ export function BalloonToolCall({ line, waiting = false, }) {
64
66
  * starts and once when it ends, never while it runs nor while words stream.
65
67
  */
66
68
  export function ToolLineAnnouncer({ line, }) {
69
+ const chatText = useChatWords();
67
70
  const last = useRef(undefined);
68
71
  const [said, setSaid] = useState('');
69
72
  useEffect(() => {
70
- const words = toolAnnouncement(line, last.current);
73
+ const words = toolAnnouncement(line, last.current, chatText);
71
74
  if (line && words) {
72
75
  last.current = { id: line.id, phase: line.phase };
73
76
  setSaid(words);
@@ -80,6 +83,7 @@ export function ToolLineAnnouncer({ line, }) {
80
83
  * agent is at work — still for a reader who asks for reduced motion.
81
84
  */
82
85
  export function BalloonNow({ busy = false }) {
86
+ const chatText = useChatWords();
83
87
  return (_jsxs(Box, { as: "span", "data-balloon-now": busy ? 'busy' : '', sx: {
84
88
  display: 'inline-flex',
85
89
  alignItems: 'center',
@@ -105,17 +109,20 @@ export function BalloonNow({ busy = false }) {
105
109
  '@media (prefers-reduced-motion: reduce)': {
106
110
  '& [data-balloon-now-dot]': { animation: 'none' },
107
111
  },
108
- }, children: [_jsx(Box, { as: "span", "data-balloon-now-dot": "", "aria-hidden": "true" }), "Now"] }));
112
+ }, children: [_jsx(Box, { as: "span", "data-balloon-now-dot": "", "aria-hidden": "true" }), chatText.now] }));
109
113
  }
110
114
  /** How the `history` balloon names itself: "Conversation · 6". */
111
- export function conversationHeaderText(count) {
112
- return count > 0 ? `Conversation · ${count}` : 'Conversation';
115
+ export function conversationHeaderText(count, chatText = ENGLISH_CHAT_WORDS) {
116
+ return count > 0
117
+ ? `${chatText.conversation} · ${count}`
118
+ : chatText.conversation;
113
119
  }
114
120
  /**
115
121
  * The `history` balloon's header: what it holds — the conversation, and how
116
122
  * many messages — over the scrolled history and the composer.
117
123
  */
118
124
  export function ConversationBalloonHeader({ count, }) {
125
+ const chatText = useChatWords();
119
126
  return (_jsx(Box, { "data-balloon-header": "", sx: {
120
127
  flexShrink: 0,
121
128
  display: 'flex',
@@ -130,7 +137,7 @@ export function ConversationBalloonHeader({ count, }) {
130
137
  bg: 'canvas.default',
131
138
  borderTopLeftRadius: 'var(--theme-radius-bubble, 16px)',
132
139
  borderTopRightRadius: 'var(--theme-radius-bubble, 16px)',
133
- }, children: _jsx(Text, { sx: { fontSize: 0, fontWeight: 'semibold', color: 'fg.muted' }, children: conversationHeaderText(count) }) }));
140
+ }, children: _jsx(Text, { sx: { fontSize: 0, fontWeight: 'semibold', color: 'fg.muted' }, children: conversationHeaderText(count, chatText) }) }));
134
141
  }
135
142
  /**
136
143
  * What the `current` balloon holds: *Now*, then the one thing being said or
@@ -4,6 +4,7 @@ import { createPortal } from 'react-dom';
4
4
  import { Button, Dialog, IconButton } from '@primer/react';
5
5
  import { Box } from '@datalayer/primer-addons';
6
6
  import { ScreenFullIcon, ScreenNormalIcon, XIcon, } from '@primer/octicons-react';
7
+ import { useChatWords } from '../ChatLanguage';
7
8
  /**
8
9
  * Expands the visual of the balloon it is in: what its compact form (a
9
10
  * notebook's preview) calls when it is clicked. None outside such a balloon.
@@ -15,7 +16,8 @@ export function useBalloonExpand() {
15
16
  }
16
17
  /** The balloon's *Expand* button. */
17
18
  export function BalloonExpandButton({ title, onExpand, }) {
18
- return (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: ScreenFullIcon, "aria-label": `Expand ${title}`, "data-balloon-expand": "", onClick: onExpand, sx: { alignSelf: 'flex-start', px: 1 }, children: "Expand" }));
19
+ const chatText = useChatWords();
20
+ return (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: ScreenFullIcon, "aria-label": chatText.expandWhat(title), "data-balloon-expand": "", onClick: onExpand, sx: { alignSelf: 'flex-start', px: 1 }, children: chatText.expand }));
19
21
  }
20
22
  /** How large the dialog is when no target is named: most of the window. */
21
23
  export const EXPANDED_DIALOG_SIZE = { width: '80vw', height: '80vh' };
@@ -12,7 +12,7 @@
12
12
  * @module chat/assistant/ConversationBalloon
13
13
  */
14
14
  import type { JSX } from 'react';
15
- import { type BalloonApproval } from './state';
15
+ import type { BalloonApproval } from './state';
16
16
  /** The open balloon's width, in pixels. */
17
17
  export declare const CONVERSATION_BALLOON_WIDTH = 380;
18
18
  /** The open balloon's tallest, in pixels, and its share of the window. */
@@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react';
3
3
  import { Button, IconButton, Text } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
5
  import { XIcon } from '@primer/octicons-react';
6
- import { ASSISTANT_WORDS } from './state';
6
+ import { useChatWords } from '../ChatLanguage';
7
7
  /** The open balloon's width, in pixels. */
8
8
  export const CONVERSATION_BALLOON_WIDTH = 380;
9
9
  /** The open balloon's tallest, in pixels, and its share of the window. */
@@ -71,6 +71,7 @@ export function ConversationBalloonClose({ name, onClose, }) {
71
71
  * wait (T-23). Brought into view when it arrives.
72
72
  */
73
73
  export function BalloonApprovalMessage({ approval, }) {
74
+ const chatText = useChatWords();
74
75
  const ref = useRef(null);
75
76
  useEffect(() => {
76
77
  ref.current?.scrollIntoView?.({ block: 'nearest' });
@@ -84,7 +85,7 @@ export function BalloonApprovalMessage({ approval, }) {
84
85
  borderColor: 'attention.muted',
85
86
  borderRadius: 'var(--theme-radius-bubble, 16px)',
86
87
  fontSize: 1,
87
- }, children: [_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: ASSISTANT_WORDS.approval }), _jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2, alignItems: 'center' }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny }), approval.others > 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })] }) }));
88
+ }, children: [_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: chatText.waitingForYou }), _jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2, alignItems: 'center' }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: chatText.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: chatText.deny }), approval.others > 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: chatText.moreWaiting(approval.others) })) : null] })] }) }));
88
89
  }
89
90
  /** How many more wait, in two words: "2 more". */
90
91
  export function moreWaiting(others) {
@@ -26,7 +26,7 @@
26
26
  * @module chat/assistant/SpeechBalloon
27
27
  */
28
28
  import type { JSX, ReactNode } from 'react';
29
- import { type BalloonApproval } from './state';
29
+ import type { BalloonApproval } from './state';
30
30
  import type { DisplayItem } from '../../types/chat';
31
31
  import type { DecisionAsker } from './decisions';
32
32
  import { type BalloonDisplay, type BalloonToolLine } from './toolLine';
@@ -3,16 +3,16 @@ import { useEffect, useRef, useState } from 'react';
3
3
  import { Button, IconButton, Text } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
5
  import { XIcon } from '@primer/octicons-react';
6
- import { ASSISTANT_WORDS } from './state';
7
- import { moreWaiting } from './ConversationBalloon';
8
6
  import { DecisionAsk } from './DecisionAsk';
9
7
  import { BalloonChatItems, BalloonToolCall, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
10
8
  import { conversationCount, withToolCall, } from './toolLine';
11
9
  import { BalloonExpandButton } from './BalloonVisual';
12
10
  import { ScreenFullIcon } from '@primer/octicons-react';
11
+ import { useChatWords } from '../ChatLanguage';
13
12
  /** The suggestions, as chips: each sends its prompt. */
14
13
  export function BalloonSuggestions({ suggestions, onSuggestion, }) {
15
- return (_jsx(Box, { role: "group", "aria-label": "Suggestions", "data-balloon-suggestions": "", sx: { display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }, children: suggestions.map(suggestion => (_jsx(Button, { size: "small", "data-balloon-suggestion": suggestion.label, "aria-label": `Ask: ${suggestion.prompt}`, title: suggestion.prompt, onClick: () => onSuggestion(suggestion), sx: { borderRadius: 999 }, children: suggestion.label }, suggestion.label))) }));
14
+ const chatText = useChatWords();
15
+ return (_jsx(Box, { role: "group", "aria-label": chatText.suggestions, "data-balloon-suggestions": "", sx: { display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }, children: suggestions.map(suggestion => (_jsx(Button, { size: "small", "data-balloon-suggestion": suggestion.label, "aria-label": `Ask: ${suggestion.prompt}`, title: suggestion.prompt, onClick: () => onSuggestion(suggestion), sx: { borderRadius: 999 }, children: suggestion.label }, suggestion.label))) }));
16
16
  }
17
17
  /** How tall the listed conversation grows before it scrolls, in pixels. */
18
18
  export const BALLOON_HISTORY_MAX_HEIGHT = 220;
@@ -29,9 +29,11 @@ export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
29
29
  * scrolled there.
30
30
  */
31
31
  export function BalloonHistoryLarge({ history, tool, attachment, }) {
32
- return (_jsxs(Box, { role: "log", "aria-label": "Conversation", "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(BalloonChatItems, { items: withToolCall(history, tool), density: "comfortable" }), attachment ? _jsx(Box, { children: attachment }) : null] }));
32
+ const chatText = useChatWords();
33
+ return (_jsxs(Box, { role: "log", "aria-label": chatText.conversation, "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(BalloonChatItems, { items: withToolCall(history, tool), density: "comfortable" }), attachment ? _jsx(Box, { children: attachment }) : null] }));
33
34
  }
34
35
  function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, tool, waiting = false, }) {
36
+ const chatText = useChatWords();
35
37
  const listRef = useRef(null);
36
38
  const newest = history[history.length - 1]?.id;
37
39
  useEffect(() => {
@@ -67,7 +69,7 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
67
69
  fontWeight: 'semibold',
68
70
  color: 'fg.muted',
69
71
  '&:hover': { textDecoration: 'underline' },
70
- }, children: conversationHeaderText(conversationCount(history)) }), onExpand ? (_jsx(IconButton, { icon: ScreenFullIcon, size: "small", variant: "invisible", "aria-label": "Expand the conversation", "data-balloon-history-expand": "", onClick: onExpand, sx: { position: 'absolute', top: '4px', right: '28px' } })) : null, _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
72
+ }, children: conversationHeaderText(conversationCount(history), chatText) }), onExpand ? (_jsx(IconButton, { icon: ScreenFullIcon, size: "small", variant: "invisible", "aria-label": chatText.expandConversation, "data-balloon-history-expand": "", onClick: onExpand, sx: { position: 'absolute', top: '4px', right: '28px' } })) : null, _jsx(Box, { ref: listRef, role: "log", "aria-label": chatText.conversation, "data-balloon-history-list": "", sx: {
71
73
  maxHeight,
72
74
  overflowY: 'auto',
73
75
  display: 'flex',
@@ -76,6 +78,7 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
76
78
  }, children: _jsx(BalloonChatItems, { items: withToolCall(history, tool), waiting: waiting }) })] }));
77
79
  }
78
80
  export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, display = 'history', tool, speaking = false, busy = false, attachment, history, onExpand, expandTitle, suggestions, onSuggestion, waiting = false, fullText, onExpandHistory, }) {
81
+ const chatText = useChatWords();
79
82
  // *more*: the words whole, when they were cut or overflow their lines.
80
83
  const [whole, setWhole] = useState(false);
81
84
  const [overflowing, setOverflowing] = useState(false);
@@ -175,7 +178,7 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
175
178
  textAlign: 'left',
176
179
  cursor: 'pointer',
177
180
  '&:hover': { textDecoration: 'underline' },
178
- }, children: _jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text }) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), current && !approval && !tool && (cut || overflowing || whole) ? (_jsx(Box, { as: "button", type: "button", "aria-expanded": whole, "data-balloon-whole": whole ? 'less' : 'more', onClick: () => setWhole(!whole), sx: {
181
+ }, children: _jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text }) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": chatText.dismiss, "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), current && !approval && !tool && (cut || overflowing || whole) ? (_jsx(Box, { as: "button", type: "button", "aria-expanded": whole, "data-balloon-whole": whole ? 'less' : 'more', onClick: () => setWhole(!whole), sx: {
179
182
  m: 0,
180
183
  mt: 1,
181
184
  p: 0,
@@ -186,6 +189,6 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
186
189
  fontSize: 0,
187
190
  cursor: 'pointer',
188
191
  '&:hover': { textDecoration: 'underline' },
189
- }, children: whole ? 'less' : 'more' })) : null, !current && !listed && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(BalloonChatItems, { items: [], waiting: true }) })) : null, approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), offered && onSuggestion ? (_jsx(BalloonSuggestions, { suggestions: offered, onSuggestion: onSuggestion })) : null, attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? 'the visual', onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
192
+ }, children: whole ? chatText.showLess : chatText.showMore })) : null, !current && !listed && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(BalloonChatItems, { items: [], waiting: true }) })) : null, approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: chatText.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: chatText.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: chatText.moreWaiting(approval.others) })) : null] })), offered && onSuggestion ? (_jsx(BalloonSuggestions, { suggestions: offered, onSuggestion: onSuggestion })) : null, attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? chatText.theVisual, onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
190
193
  }
191
194
  export default SpeechBalloon;
@@ -1,4 +1,5 @@
1
1
  import type { DisplayItem, ToolCallMessage } from '../../types/chat';
2
+ import { type ChatWords } from '../words';
2
3
  /**
3
4
  * How the balloon shows the conversation: `history`, every message,
4
5
  * scrolled, the composer last; `current`, only what it says or does now, in
@@ -32,14 +33,17 @@ export interface BalloonToolLine {
32
33
  * prefix (`odoo__list_invoices` is `list_invoices`).
33
34
  */
34
35
  export declare function toolDisplayName(tool: string): string;
35
- /** The three parts a tool line is said in: before the name, the name, after it. */
36
- export declare function toolLineParts(line: BalloonToolLine): {
36
+ /**
37
+ * The three parts a tool line is said in: before the name, the name, after
38
+ * it — in the chat's language (P-26), English unless said.
39
+ */
40
+ export declare function toolLineParts(line: BalloonToolLine, chatText?: ChatWords): {
37
41
  before: string;
38
42
  name: string;
39
43
  after: string;
40
44
  };
41
45
  /** A tool line in plain words: "Using list_invoices…". */
42
- export declare function toolLineText(line: BalloonToolLine): string;
46
+ export declare function toolLineText(line: BalloonToolLine, chatText?: ChatWords): string;
43
47
  /**
44
48
  * The tool line of the conversation's newest item, when that item is a tool
45
49
  * call: running until it is complete or failed. Once words follow it, the
@@ -62,7 +66,7 @@ export declare function toolLineOfStep(step: ToolStep, name?: string): BalloonTo
62
66
  export declare function toolAnnouncement(line: BalloonToolLine | undefined, previous: {
63
67
  id: string;
64
68
  phase: ToolLinePhase;
65
- } | undefined): string | undefined;
69
+ } | undefined, chatText?: ChatWords): string | undefined;
66
70
  /**
67
71
  * How many messages the conversation holds, as the `history` balloon's
68
72
  * header counts them: the person's and the agent's, not the tool calls.
@@ -11,6 +11,7 @@
11
11
  * @module chat/assistant/toolLine
12
12
  */
13
13
  import { BACKEND_TOOL_CATALOG } from '../../specs/backendTools';
14
+ import { ENGLISH_CHAT_WORDS } from '../words';
14
15
  /** The two displays, in the Appspec's order (`interface.balloon`). */
15
16
  export const BALLOON_DISPLAYS = [
16
17
  'history',
@@ -41,23 +42,26 @@ export function toolDisplayName(tool) {
41
42
  const at = tool.indexOf('__');
42
43
  return at > 0 ? tool.slice(at + 2) : tool;
43
44
  }
44
- /** The three parts a tool line is said in: before the name, the name, after it. */
45
- export function toolLineParts(line) {
45
+ /**
46
+ * The three parts a tool line is said in: before the name, the name, after
47
+ * it — in the chat's language (P-26), English unless said.
48
+ */
49
+ export function toolLineParts(line, chatText = ENGLISH_CHAT_WORDS) {
46
50
  if (line.phase === 'running' && line.words) {
47
51
  return { before: '', name: '', after: line.words };
48
52
  }
49
53
  switch (line.phase) {
50
54
  case 'running':
51
- return { before: 'Using ', name: line.name, after: '…' };
55
+ return { ...chatText.toolRunning, name: line.name };
52
56
  case 'done':
53
- return { before: 'Done: ', name: line.name, after: '' };
57
+ return { ...chatText.toolDone, name: line.name };
54
58
  case 'failed':
55
- return { before: '', name: line.name, after: ' failed' };
59
+ return { ...chatText.toolFailedLine, name: line.name };
56
60
  }
57
61
  }
58
62
  /** A tool line in plain words: "Using list_invoices…". */
59
- export function toolLineText(line) {
60
- const { before, name, after } = toolLineParts(line);
63
+ export function toolLineText(line, chatText = ENGLISH_CHAT_WORDS) {
64
+ const { before, name, after } = toolLineParts(line, chatText);
61
65
  return `${before}${name}${after}`;
62
66
  }
63
67
  /**
@@ -96,14 +100,14 @@ export function toolLineOfStep(step, name = toolDisplayName(step.name)) {
96
100
  * What a screen reader hears of a tool line: once when the call starts and
97
101
  * once when it ends — a new id or a new phase — never while it runs.
98
102
  */
99
- export function toolAnnouncement(line, previous) {
103
+ export function toolAnnouncement(line, previous, chatText = ENGLISH_CHAT_WORDS) {
100
104
  if (!line) {
101
105
  return undefined;
102
106
  }
103
107
  if (previous && previous.id === line.id && previous.phase === line.phase) {
104
108
  return undefined;
105
109
  }
106
- return toolLineText(line);
110
+ return toolLineText(line, chatText);
107
111
  }
108
112
  /**
109
113
  * How many messages the conversation holds, as the `history` balloon's
@@ -58,12 +58,13 @@ const EphemeralDocument = React.lazy(() => import('../document/EphemeralDocument
58
58
  default: m.EphemeralDocument,
59
59
  })));
60
60
  import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHooks';
61
- import { useAgentInspectorSink } from '../../components/inspector/agentInspector';
62
- import { chatInspectorRecorder, } from '../../components/inspector/chatInspect';
61
+ import { useAgentInspectorTracer } from '../../components/inspector/agentSpans';
62
+ import { chatSpanRecorder, } from '../../components/inspector/chatSpans';
63
63
  import { loopMessageChange, speakerOf, withLoopMessage } from './loopMessage';
64
64
  import { loopStepOf, withLoopStep } from './loopStep';
65
65
  import { applyLoopElement, loopElementChange } from './loopElement';
66
66
  import { loopWindowMessage, tellLoopWindow } from './loopWindow';
67
+ import { useChatWords } from '../ChatLanguage';
67
68
  // Tracks pending prompts already auto-sent for a given conversation scope.
68
69
  // This prevents layout-driven unmount/remount cycles from re-sending prompts.
69
70
  const sentPendingPromptKeys = new Set();
@@ -528,6 +529,7 @@ runtimeId, thread, historyEndpoint, historyAuthToken: _historyAuthToken,
528
529
  pendingPrompt, contextSnapshot: externalContextSnapshot, mcpStatusData, sandboxStatusData,
529
530
  // Tool approval banner
530
531
  showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApproveApproval: onApproveApprovalProp, onRejectApproval: onRejectApprovalProp, }) {
532
+ const chatText = useChatWords();
531
533
  useEffect(() => {
532
534
  setupPrimerPortals();
533
535
  }, []);
@@ -1608,22 +1610,22 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1608
1610
  onToolCallStartRef.current = onToolCallStart;
1609
1611
  const onToolCallCompleteRef = useRef(onToolCallComplete);
1610
1612
  onToolCallCompleteRef.current = onToolCallComplete;
1611
- // The Agent Inspector: the turns and tool calls, recorded as they happen.
1612
- const inspectorFromContext = useAgentInspectorSink();
1613
- const inspectorSink = inspectorGiven ?? inspectorFromContext;
1613
+ // The Agent Inspector: the turns and tool calls, as spans, as they happen.
1614
+ const inspectorFromContext = useAgentInspectorTracer();
1615
+ const inspectorTracer = inspectorGiven ?? inspectorFromContext;
1614
1616
  const inspectorActorRef = useRef('Agent');
1615
1617
  inspectorActorRef.current =
1616
1618
  (typeof title === 'string' && title) || activeAgentId || 'Agent';
1617
1619
  const recorderRef = useRef(null);
1618
- if (inspectorSink && recorderRef.current?.sink !== inspectorSink) {
1620
+ if (inspectorTracer && recorderRef.current?.tracer !== inspectorTracer) {
1619
1621
  recorderRef.current = {
1620
- sink: inspectorSink,
1621
- recorder: chatInspectorRecorder(inspectorSink, () => inspectorActorRef.current, {
1622
+ tracer: inspectorTracer,
1623
+ recorder: chatSpanRecorder(inspectorTracer, () => inspectorActorRef.current, {
1622
1624
  frontendTools: () => (frontendToolsRef.current ?? []).map(tool => tool.name),
1623
1625
  }),
1624
1626
  };
1625
1627
  }
1626
- else if (!inspectorSink) {
1628
+ else if (!inspectorTracer) {
1627
1629
  recorderRef.current = null;
1628
1630
  }
1629
1631
  const inspectAnswerRef = useRef('');
@@ -3025,7 +3027,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3025
3027
  }
3026
3028
  },
3027
3029
  onError: (error) => {
3028
- const errorContent = `Error: ${error.message}`;
3030
+ const errorContent = chatText.error(error.message);
3029
3031
  setDisplayItems(prev => prev.map(item => item.id === assistantMessageId
3030
3032
  ? { ...item, content: errorContent }
3031
3033
  : item));
@@ -3077,7 +3079,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3077
3079
  catch (err) {
3078
3080
  if (err.name !== 'AbortError') {
3079
3081
  console.error('[ChatBase] Send error:', err);
3080
- const errorMessage = createAssistantMessage(`Error: ${err.message}`);
3082
+ const errorMessage = createAssistantMessage(chatText.error(err.message));
3081
3083
  setDisplayItems(prev => [...prev, errorMessage]);
3082
3084
  setError(err);
3083
3085
  }
@@ -3110,6 +3112,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3110
3112
  getEnabledMcpToolNames,
3111
3113
  getEnabledSkillIds,
3112
3114
  noteThreadStarted,
3115
+ chatText,
3113
3116
  ]);
3114
3117
  // Send pending prompt once history loaded and adapter/handler available
3115
3118
  useEffect(() => {
@@ -3,6 +3,7 @@ import { Text, LabelGroup, Label, Truncate, ThemeProvider, } from '@primer/react
3
3
  import { Box } from '@datalayer/primer-addons';
4
4
  import { AiAgentIcon } from '@datalayer/icons-react';
5
5
  import { groupSuggestions } from './groupSuggestions';
6
+ import { useChatWords } from '../ChatLanguage';
6
7
  export { groupSuggestions } from './groupSuggestions';
7
8
  /**
8
9
  * How wide a suggestion chip stands, in pixels — fixed, not a cap.
@@ -21,6 +22,7 @@ export const SUGGESTION_CHIP_WIDTH = 140;
21
22
  // Component
22
23
  // ---------------------------------------------------------------------------
23
24
  export function ChatEmptyState({ emptyState, brandIcon, description, suggestions, onSuggestionSubmit, onSuggestionFill, submitOnSuggestionClick = true, }) {
25
+ const chatText = useChatWords();
24
26
  // Custom render takes precedence
25
27
  if (emptyState?.render) {
26
28
  return _jsx(_Fragment, { children: emptyState.render() });
@@ -107,7 +109,7 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
107
109
  }, children: section.title }), section.subtitle ? (_jsx(Text, { sx: { fontSize: level === 0 ? 1 : 0 }, children: section.subtitle })) : null, (() => {
108
110
  const own = (suggestions ?? []).filter(suggestion => suggestion.group === section.group);
109
111
  return own.length > 0 ? chips(own) : null;
110
- })()] }, section.group)))) : (_jsxs(_Fragment, { children: [emptyState?.icon || brandIcon || _jsx(AiAgentIcon, { colored: true, size: 48 }), _jsx(Text, { sx: { fontSize: 2 }, children: emptyState?.title || 'Start a conversation' }), (emptyState?.subtitle || description) && (_jsx(Text, { sx: { fontSize: 1 }, children: emptyState?.subtitle || description }))] })), blocks.map(block => (_jsxs(Box, { sx: {
112
+ })()] }, section.group)))) : (_jsxs(_Fragment, { children: [emptyState?.icon || brandIcon || _jsx(AiAgentIcon, { colored: true, size: 48 }), _jsx(Text, { sx: { fontSize: 2 }, children: emptyState?.title || chatText.startConversation }), (emptyState?.subtitle || description) && (_jsx(Text, { sx: { fontSize: 1 }, children: emptyState?.subtitle || description }))] })), blocks.map(block => (_jsxs(Box, { sx: {
111
113
  display: 'flex',
112
114
  flexDirection: 'column',
113
115
  alignItems: 'center',