@alpaca-editor/core 1.0.4133 → 1.0.4134

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.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
3
3
  import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, } from "lucide-react";
4
4
  import { DancingDots } from "./DancingDots";
@@ -18,6 +18,106 @@ import { Tooltip, TooltipTrigger, TooltipContent, } from "../../components/ui/to
18
18
  const UserMessage = ({ message }) => {
19
19
  return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "flex-shrink-0", children: _jsx(User, { className: "h-6 w-6 text-blue-600", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-900", children: "You" }), message.createdDate && (_jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose-sm max-w-none text-xs text-gray-700 select-text", children: message.content })] })] }));
20
20
  };
21
+ // Helper to extract todos from potentially incomplete JSON during streaming
22
+ const extractPartialTodos = (jsonText) => {
23
+ // First try to parse complete JSON
24
+ try {
25
+ const parsed = JSON.parse(jsonText);
26
+ return Array.isArray(parsed) ? parsed : parsed?.items || [];
27
+ }
28
+ catch (e) {
29
+ // If JSON is incomplete, try to extract whatever todo items we can find
30
+ const items = [];
31
+ // Look for individual todo objects in the partial JSON
32
+ // Match patterns like: { "text": "...", "done": false, "note": "..." }
33
+ // Handle various field orderings (text can be anywhere in the object)
34
+ const textPattern = /"text"\s*:\s*"([^"]+)"/g;
35
+ const textMatches = [];
36
+ let textMatch;
37
+ while ((textMatch = textPattern.exec(jsonText)) !== null) {
38
+ if (textMatch[1]) {
39
+ textMatches.push({
40
+ text: textMatch[1],
41
+ startIdx: textMatch.index,
42
+ });
43
+ }
44
+ }
45
+ // For each text field found, try to find the enclosing object
46
+ for (const { text, startIdx } of textMatches) {
47
+ // Find the opening brace before this text field
48
+ let openBrace = -1;
49
+ for (let i = startIdx - 1; i >= 0; i--) {
50
+ if (jsonText[i] === "{") {
51
+ openBrace = i;
52
+ break;
53
+ }
54
+ if (jsonText[i] === "}")
55
+ break; // Hit another object's end
56
+ }
57
+ if (openBrace === -1)
58
+ continue;
59
+ // Find the closing brace after this text field
60
+ let closeBrace = -1;
61
+ let depth = 0;
62
+ for (let i = openBrace; i < jsonText.length; i++) {
63
+ if (jsonText[i] === "{")
64
+ depth++;
65
+ if (jsonText[i] === "}") {
66
+ depth--;
67
+ if (depth === 0) {
68
+ closeBrace = i;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ // Extract the object and try to parse it
74
+ const objStr = closeBrace !== -1
75
+ ? jsonText.substring(openBrace, closeBrace + 1)
76
+ : jsonText.substring(openBrace) + "}"; // Try to close incomplete object
77
+ try {
78
+ const obj = JSON.parse(objStr);
79
+ if (obj.text) {
80
+ items.push({
81
+ text: obj.text,
82
+ done: obj.done === true,
83
+ note: obj.note || undefined,
84
+ });
85
+ }
86
+ }
87
+ catch (e) {
88
+ // Skip malformed objects
89
+ }
90
+ }
91
+ // Also try to extract from partial objects at the end
92
+ // Look for the last opening brace and try to parse up to where we have valid content
93
+ const lines = jsonText.split("\n");
94
+ for (let i = lines.length - 1; i >= 0; i--) {
95
+ const partialJson = lines.slice(0, i + 1).join("\n");
96
+ // Try to close any open braces/brackets
97
+ let testJson = partialJson;
98
+ const openBraces = (testJson.match(/\{/g) || []).length;
99
+ const closeBraces = (testJson.match(/\}/g) || []).length;
100
+ const openBrackets = (testJson.match(/\[/g) || []).length;
101
+ const closeBrackets = (testJson.match(/\]/g) || []).length;
102
+ // Add missing closing characters
103
+ testJson += "]".repeat(openBrackets - closeBrackets);
104
+ testJson += "}".repeat(openBraces - closeBraces);
105
+ try {
106
+ const parsed = JSON.parse(testJson);
107
+ const partialItems = Array.isArray(parsed)
108
+ ? parsed
109
+ : parsed?.items || [];
110
+ if (partialItems.length > items.length) {
111
+ return partialItems;
112
+ }
113
+ }
114
+ catch (e) {
115
+ continue;
116
+ }
117
+ }
118
+ return items;
119
+ }
120
+ };
21
121
  const extractTodosFromMessages = (messages) => {
22
122
  const todos = [];
23
123
  const fencedTodoToken = "```todo_list";
@@ -48,13 +148,20 @@ const extractTodosFromMessages = (messages) => {
48
148
  break;
49
149
  try {
50
150
  let jsonText = "";
151
+ let isComplete = true;
51
152
  if (isFenced) {
52
153
  const afterToken = todoStart + fencedTodoToken.length;
53
154
  const closePos = content.indexOf("```", afterToken);
54
- if (closePos === -1)
55
- break;
56
- jsonText = content.slice(afterToken, closePos).trim();
57
- cursor = closePos + 3;
155
+ if (closePos === -1) {
156
+ // Incomplete fenced block - extract what we have so far
157
+ jsonText = content.slice(afterToken).trim();
158
+ isComplete = false;
159
+ cursor = content.length; // Process till end
160
+ }
161
+ else {
162
+ jsonText = content.slice(afterToken, closePos).trim();
163
+ cursor = closePos + 3;
164
+ }
58
165
  }
59
166
  else {
60
167
  const afterToken = todoStart + plainTodoToken.length;
@@ -74,24 +181,54 @@ const extractTodosFromMessages = (messages) => {
74
181
  }
75
182
  }
76
183
  }
77
- if (braceEnd === -1)
78
- break;
79
- jsonText = content.slice(braceStart, braceEnd + 1).trim();
80
- cursor = braceEnd + 1;
184
+ if (braceEnd === -1) {
185
+ // Incomplete JSON - extract what we have
186
+ jsonText = content.slice(braceStart).trim();
187
+ isComplete = false;
188
+ cursor = content.length;
189
+ }
190
+ else {
191
+ jsonText = content.slice(braceStart, braceEnd + 1).trim();
192
+ cursor = braceEnd + 1;
193
+ }
81
194
  }
82
- const parsed = JSON.parse(jsonText);
83
- const todoItems = Array.isArray(parsed) ? parsed : parsed?.items || [];
84
- const title = Array.isArray(parsed) ? undefined : parsed?.title;
195
+ // Use the partial extraction helper for incomplete JSON
196
+ const todoItems = isComplete
197
+ ? (() => {
198
+ try {
199
+ const parsed = JSON.parse(jsonText);
200
+ return Array.isArray(parsed) ? parsed : parsed?.items || [];
201
+ }
202
+ catch (e) {
203
+ return [];
204
+ }
205
+ })()
206
+ : extractPartialTodos(jsonText);
207
+ const title = (() => {
208
+ try {
209
+ const parsed = JSON.parse(jsonText);
210
+ return Array.isArray(parsed) ? undefined : parsed?.title;
211
+ }
212
+ catch (e) {
213
+ return undefined;
214
+ }
215
+ })();
85
216
  todoItems.forEach((item) => {
86
217
  if (!item)
87
218
  return;
88
- const text = item.text || item.label || String(item.task || item.title || "");
219
+ const text = item.text ||
220
+ item.content ||
221
+ item.label ||
222
+ String(item.task || item.title || "");
89
223
  if (!text)
90
224
  return;
91
225
  todos.push({
92
226
  id: item.id,
93
227
  text,
94
- done: !!(item.done ?? item.completed ?? item.checked),
228
+ done: !!(item.done ??
229
+ item.completed ??
230
+ item.checked ??
231
+ item.status === "completed"),
95
232
  note: item.note || item.description,
96
233
  messageId: message.id,
97
234
  sourceTitle: title,
@@ -107,25 +244,97 @@ const extractTodosFromMessages = (messages) => {
107
244
  return todos;
108
245
  };
109
246
  // TodoListPanel component
110
- const TodoListPanel = ({ messages }) => {
247
+ const TodoListPanel = ({ messages, agentMetadata, }) => {
111
248
  const [isExpanded, setIsExpanded] = useState(true);
112
- const todos = useMemo(() => extractTodosFromMessages(messages), [messages]);
113
- // Check if there's an active streaming message with todo content
249
+ const todos = useMemo(() => {
250
+ // First try to get todos from agent metadata (real-time updates)
251
+ const metadataTodos = (() => {
252
+ try {
253
+ const context = agentMetadata?.additionalData?.context;
254
+ const todoList = context?.todoList;
255
+ if (todoList?.items && Array.isArray(todoList.items)) {
256
+ return todoList.items
257
+ .map((item, idx) => ({
258
+ id: item.id || `metadata-${idx}`,
259
+ text: item.text ||
260
+ item.label ||
261
+ String(item.task || item.title || ""),
262
+ done: !!(item.done ?? item.completed ?? item.checked),
263
+ note: item.note || item.description,
264
+ messageId: undefined,
265
+ sourceTitle: todoList.title,
266
+ }))
267
+ .filter((item) => item.text);
268
+ }
269
+ }
270
+ catch (e) {
271
+ // Fallback to extracting from messages
272
+ }
273
+ return null;
274
+ })();
275
+ // If we have metadata todos, use them; otherwise extract from messages
276
+ if (metadataTodos && metadataTodos.length > 0) {
277
+ return metadataTodos;
278
+ }
279
+ return extractTodosFromMessages(messages);
280
+ }, [messages, agentMetadata]);
281
+ // Check if there's an active streaming message with incomplete todo content
114
282
  const isUpdating = useMemo(() => {
115
283
  return messages.some((msg) => {
116
284
  if (msg.role !== "assistant" || msg.isCompleted)
117
285
  return false;
118
286
  const content = msg.content || "";
119
- return content.includes("```todo_list") || content.includes("todo_list");
287
+ // Check for incomplete fenced todo blocks
288
+ const fencedStart = content.indexOf("```todo_list");
289
+ if (fencedStart !== -1) {
290
+ const afterStart = fencedStart + "```todo_list".length;
291
+ const closePos = content.indexOf("```", afterStart);
292
+ if (closePos === -1) {
293
+ // Incomplete fenced block
294
+ return true;
295
+ }
296
+ }
297
+ // Check for incomplete plain todo blocks
298
+ const plainStart = content.indexOf("todo_list");
299
+ if (plainStart !== -1 && plainStart !== fencedStart) {
300
+ const before = plainStart > 0 ? content[plainStart - 1] : "\n";
301
+ if (before === "\n" || before === "\r" || plainStart === 0) {
302
+ const braceStart = content.indexOf("{", plainStart);
303
+ if (braceStart !== -1) {
304
+ let depth = 0;
305
+ let braceEnd = -1;
306
+ for (let i = braceStart; i < content.length; i++) {
307
+ if (content[i] === "{")
308
+ depth++;
309
+ if (content[i] === "}") {
310
+ depth--;
311
+ if (depth === 0) {
312
+ braceEnd = i;
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ if (braceEnd === -1) {
318
+ // Incomplete plain block
319
+ return true;
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return false;
120
325
  });
121
326
  }, [messages]);
122
327
  if (todos.length === 0 && !isUpdating)
123
328
  return null;
124
329
  const completedCount = todos.filter((t) => t.done).length;
125
330
  const totalCount = todos.length;
126
- return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsx("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: isUpdating && todos.length === 0 ? (_jsxs("div", { className: "flex items-center justify-center gap-2 py-4 text-xs text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), _jsx("span", { children: "Loading todo list..." })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-xs", children: [_jsx("div", { className: "flex-shrink-0 pt-0.5", children: todo.done ? (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })) : (_jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `${todo.done
127
- ? "text-gray-500 line-through"
128
- : "text-gray-900"}`, children: todo.text }), todo.note && (_jsx("div", { className: "mt-0.5 text-xs text-gray-500", children: todo.note }))] })] }, todo.id || `${todo.messageId}-${idx}`))) }), isUpdating && todos.length > 0 && (_jsxs("div", { className: "mt-2 flex items-center gap-2 rounded bg-blue-50 px-3 py-2 text-xs text-blue-700", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: "Updating todo list..." })] }))] })) }))] }));
331
+ return (_jsxs("div", { className: "border-t border-gray-200 bg-gray-50", children: [_jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "flex w-full cursor-pointer items-center justify-between px-4 py-2 text-left transition-colors hover:bg-gray-100", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ListTodo, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }), _jsx("span", { className: "text-xs font-medium text-gray-700", children: "Todo List" }), isUpdating ? (_jsxs("span", { className: "flex items-center gap-1 text-xs text-blue-600", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), "Updating..."] })) : (_jsxs("span", { className: "text-xs text-gray-500", children: [completedCount, "/", totalCount, " completed"] }))] }), isExpanded ? (_jsx(ChevronUp, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 })) : (_jsx(ChevronDown, { className: "h-4 w-4 text-gray-500", strokeWidth: 1 }))] }), isExpanded && (_jsxs("div", { className: "max-h-64 overflow-y-auto px-4 pb-3", children: [todos.length > 0 && (_jsx("div", { className: "space-y-1.5", children: todos.map((todo, idx) => (_jsxs("div", { className: "flex items-start gap-2 rounded bg-white p-2 text-xs", children: [_jsx("div", { className: "flex-shrink-0 pt-0.5", children: todo.done ? (_jsx("div", { className: "flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500", children: _jsx("svg", { className: "h-3 w-3 text-white", fill: "none", strokeWidth: 2, stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) })) : (_jsx("div", { className: "h-4 w-4 rounded border-2 border-gray-300" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `${todo.done
332
+ ? "text-gray-500 line-through"
333
+ : "text-gray-900"}`, children: todo.text }), todo.note && (_jsx("div", { className: "mt-0.5 text-xs text-gray-500", children: todo.note }))] })] }, todo.id || `${todo.messageId}-${idx}`))) })), isUpdating && (_jsxs("div", { className: `flex items-center gap-2 rounded px-3 py-2 text-xs ${todos.length > 0
334
+ ? "mt-2 bg-blue-50 text-blue-700"
335
+ : "justify-center bg-white text-gray-500"}`, children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin", strokeWidth: 1 }), _jsx("span", { children: todos.length > 0
336
+ ? "Updating todo list..."
337
+ : "Loading todo list..." })] }))] }))] }));
129
338
  };
130
339
  const groupConsecutiveMessages = (agentMessages) => {
131
340
  // Work directly with the messages array - streaming messages are identified by their properties
@@ -222,7 +431,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
222
431
  const [agent, setAgent] = useState(undefined);
223
432
  const [messages, setMessages] = useState([]);
224
433
  const [prompt, setPrompt] = useState("");
225
- const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Ctrl+Enter for new line)");
434
+ const [inputPlaceholder, setInputPlaceholder] = useState("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
226
435
  const [isLoading, setIsLoading] = useState(false);
227
436
  const [isConnecting, setIsConnecting] = useState(false);
228
437
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -320,6 +529,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
320
529
  }, [messages]);
321
530
  const [error, setError] = useState(null);
322
531
  const [costLimitExceeded, setCostLimitExceeded] = useState(null);
532
+ // Live running totals from backend status updates (tokenUsage)
533
+ const [liveTotals, setLiveTotals] = useState(null);
323
534
  // Flag to track when we should create a new message
324
535
  const shouldCreateNewMessage = useRef(false);
325
536
  // Keep a ref to the current messages for immediate access
@@ -776,6 +987,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
776
987
  case "statusUpdate":
777
988
  try {
778
989
  const kind = message?.data?.kind;
990
+ // Live token usage totals update from backend
991
+ if (kind === "tokenUsage") {
992
+ const totals = message?.data?.totals;
993
+ if (totals) {
994
+ setLiveTotals({
995
+ input: Number(totals.totalInputTokens) || 0,
996
+ output: Number(totals.totalOutputTokens) || 0,
997
+ cached: Number(totals.totalCachedInputTokens) || 0,
998
+ inputCost: Number(totals.totalInputTokenCost) || 0,
999
+ outputCost: Number(totals.totalOutputTokenCost) || 0,
1000
+ cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
1001
+ totalCost: Number(totals.totalCost) || 0,
1002
+ currency: totals.currency,
1003
+ });
1004
+ // Force a re-render to update cost display immediately
1005
+ setMessages((prev) => [...prev]);
1006
+ }
1007
+ break;
1008
+ }
779
1009
  if (kind === "toolApprovalsRequired") {
780
1010
  const data = message.data || {};
781
1011
  const msgId = data.messageId;
@@ -981,6 +1211,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
981
1211
  updatedMessage.tokensUsed =
982
1212
  (updatedMessage.inputTokens || 0) +
983
1213
  (updatedMessage.outputTokens || 0);
1214
+ // Update cost data if provided in the completed event
1215
+ if (data.inputTokenCost !== undefined) {
1216
+ updatedMessage.inputTokenCost = data.inputTokenCost;
1217
+ }
1218
+ if (data.outputTokenCost !== undefined) {
1219
+ updatedMessage.outputTokenCost =
1220
+ data.outputTokenCost;
1221
+ }
1222
+ if (data.cachedInputTokenCost !== undefined ||
1223
+ data.cachedTokenCost !== undefined) {
1224
+ updatedMessage.cachedInputTokenCost =
1225
+ data.cachedInputTokenCost ?? data.cachedTokenCost;
1226
+ }
1227
+ if (data.totalCost !== undefined) {
1228
+ updatedMessage.totalCost = data.totalCost;
1229
+ }
984
1230
  // Handle content that might only be sent in the completed event
985
1231
  if (data.deltaContent && data.deltaContent.trim()) {
986
1232
  if (!data.isIncremental) {
@@ -1639,7 +1885,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
1639
1885
  }
1640
1886
  };
1641
1887
  const handleKeyPress = (e) => {
1642
- if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
1888
+ // Submit only on plain Enter (no Ctrl/Meta/Shift/Alt)
1889
+ if (e.key === "Enter" &&
1890
+ !e.ctrlKey &&
1891
+ !e.metaKey &&
1892
+ !e.shiftKey &&
1893
+ !e.altKey) {
1643
1894
  e.preventDefault();
1644
1895
  handleSubmit();
1645
1896
  }
@@ -2315,7 +2566,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2315
2566
  sendQuickMessage(text);
2316
2567
  } }, groupIndex));
2317
2568
  }
2318
- }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2569
+ }) }), _jsx("div", { className: showDots ? "visible" : "invisible", children: _jsx(DancingDots, {}) }), _jsx("div", { ref: messagesEndRef })] }), renderContextInfoBar(), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsxs("div", { className: "border-t border-gray-200 p-4", children: [_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, value: prompt, onChange: (e) => {
2319
2570
  setPrompt(e.target.value);
2320
2571
  // Reset history index when user starts typing
2321
2572
  if (currentHistoryIndex !== -1) {
@@ -2384,7 +2635,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, }) {
2384
2635
  setShowPredefined(false);
2385
2636
  if (textareaRef.current)
2386
2637
  textareaRef.current.focus();
2387
- }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: totalTokens }), (() => {
2638
+ }, children: p.title }, index))) }) })] })) : null] }), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [isVoiceSupported ? (_jsx(Button, { onClick: toggleVoice, size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isListening ? "Stop voice input" : "Start voice input", "aria-label": isListening ? "Stop voice input" : "Start voice input", "aria-pressed": isListening, children: isListening ? (_jsx(MicOff, { className: "size-3", strokeWidth: 1 })) : (_jsx(Mic, { className: "size-3", strokeWidth: 1 })) })) : null, _jsx(Button, { onClick: isExecuting ? handleStop : handleSubmit, disabled: !isExecuting && !prompt.trim(), size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", title: isExecuting ? "Stop" : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-[10px] text-gray-500", children: [_jsx(AgentCostDisplay, { totalTokens: liveTotals
2639
+ ? {
2640
+ input: liveTotals.input,
2641
+ output: liveTotals.output,
2642
+ cached: liveTotals.cached,
2643
+ inputCost: liveTotals.inputCost,
2644
+ outputCost: liveTotals.outputCost,
2645
+ cachedCost: liveTotals.cachedCost,
2646
+ totalCost: liveTotals.totalCost,
2647
+ }
2648
+ : totalTokens }), (() => {
2388
2649
  try {
2389
2650
  const s = window.__agentContextWindowStatus;
2390
2651
  if (!s || !s.contextWindowTokens)