@coral-ai/chat 0.4.1 → 0.5.0
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/index.d.ts +46 -3
- package/dist/index.js +177 -45
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { ReactNode } from 'react';
|
|
3
3
|
import { ChatFontSize, ChatFontFamily } from '@coral-ai/markdown';
|
|
4
|
+
import { CoralChartSpec } from '@coral-ai/charts';
|
|
4
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
type ErrorCategory = "auth_billing" | "rate_limit" | "overloaded" | "bad_input" | "not_found" | "server_error" | "unknown";
|
|
@@ -43,10 +44,29 @@ type ChatMessage = {
|
|
|
43
44
|
approval?: ChatApprovalRequest;
|
|
44
45
|
/**
|
|
45
46
|
* If present, this assistant message carries a multi-step task plan.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* Legacy field — new messages use `blocks` to position the todo list
|
|
48
|
+
* inline at the stream point where it first appeared. Still read by the
|
|
49
|
+
* renderer as a fallback for persisted messages saved before blocks.
|
|
48
50
|
*/
|
|
49
51
|
todos?: ChatTodo[];
|
|
52
|
+
/**
|
|
53
|
+
* If present, this assistant message carries one or more chart specs.
|
|
54
|
+
* Legacy field — new messages use `blocks` to position each chart at
|
|
55
|
+
* the stream point where it was emitted. Still read by the renderer
|
|
56
|
+
* as a fallback for persisted messages saved before blocks.
|
|
57
|
+
*/
|
|
58
|
+
charts?: CoralChartSpec[];
|
|
59
|
+
/**
|
|
60
|
+
* Ordered list of inline blocks (text, charts, todos) in the order the
|
|
61
|
+
* agent emitted them. Renderers iterate this in stream order so a chart
|
|
62
|
+
* tool call appears at the exact point in the prose where it happened,
|
|
63
|
+
* matching the convention used by Claude.ai and ChatGPT.
|
|
64
|
+
*
|
|
65
|
+
* When absent, the renderer synthesizes blocks from the legacy
|
|
66
|
+
* `content` / `charts` / `todos` fields so old persisted conversations
|
|
67
|
+
* still display correctly.
|
|
68
|
+
*/
|
|
69
|
+
blocks?: ChatBlock[];
|
|
50
70
|
/**
|
|
51
71
|
* If present, the turn that produced this assistant message failed. The
|
|
52
72
|
* message renders as an error card (with a Retry button) instead of normal
|
|
@@ -88,6 +108,22 @@ type ChatApprovalRequest = {
|
|
|
88
108
|
text?: string;
|
|
89
109
|
};
|
|
90
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* One inline block within an assistant message. Blocks are rendered in the
|
|
113
|
+
* order the agent emitted them, so charts and todo lists appear at the exact
|
|
114
|
+
* stream position where they happened (e.g. text → chart → text), rather than
|
|
115
|
+
* being pinned above the message.
|
|
116
|
+
*/
|
|
117
|
+
type ChatBlock = {
|
|
118
|
+
type: "text";
|
|
119
|
+
content: string;
|
|
120
|
+
} | {
|
|
121
|
+
type: "chart";
|
|
122
|
+
spec: CoralChartSpec;
|
|
123
|
+
} | {
|
|
124
|
+
type: "todos";
|
|
125
|
+
todos: ChatTodo[];
|
|
126
|
+
};
|
|
91
127
|
/**
|
|
92
128
|
* One step in a multi-step task plan. The agent emits the whole list on each
|
|
93
129
|
* update; the convention is exactly one item `in_progress` at a time.
|
|
@@ -116,6 +152,11 @@ type ChatChunk = string | {
|
|
|
116
152
|
} | {
|
|
117
153
|
type: "todos";
|
|
118
154
|
todos: ChatTodo[];
|
|
155
|
+
}
|
|
156
|
+
/** Appends a chart spec to the current assistant message; renders inline. */
|
|
157
|
+
| {
|
|
158
|
+
type: "chart";
|
|
159
|
+
spec: CoralChartSpec;
|
|
119
160
|
};
|
|
120
161
|
type ChatResponse = string | AsyncIterable<ChatChunk> | {
|
|
121
162
|
content: string;
|
|
@@ -478,6 +519,8 @@ type AgentSessionStore = {
|
|
|
478
519
|
declare const UPDATE_TODOS_TOOL: ToolSpec;
|
|
479
520
|
/** Built-in tool: pause and ask the user to approve / choose before continuing. */
|
|
480
521
|
declare const REQUEST_APPROVAL_TOOL: ToolSpec;
|
|
522
|
+
/** Built-in tool: render a chart inline as part of the assistant message. */
|
|
523
|
+
declare const RENDER_CHART_TOOL: ToolSpec;
|
|
481
524
|
type CreateAgentHandlerOptions = {
|
|
482
525
|
/** Provider adapter — the only provider-specific code. */
|
|
483
526
|
callModel: CallModel;
|
|
@@ -658,4 +701,4 @@ declare const Chat: typeof Chat$1 & {
|
|
|
658
701
|
declare const normalizeChatError: typeof normalizeChatError$1;
|
|
659
702
|
declare const createHandler: typeof createHandler$1;
|
|
660
703
|
|
|
661
|
-
export { type AgentHandler, type AgentMessage, type AgentSessionStore, type AgentSnapshot, type AgentTool, type ApprovalOption, type CallModel, Chat, type ChatApprovalProps, type ChatApprovalRequest, type ChatAttachButtonProps, type ChatAttachType, type ChatAttachment, type ChatCanvasProps, type ChatChunk, type ChatComponents, type ChatErrorProps, type ChatInputProps, type ChatMessage, type ChatMessageRendererProps, type ChatMessagesProps, type ChatResponse, type ChatThread, type ChatThreadsAdapter, type ChatThreadsContextValue, ChatThreadsProvider, type ChatThreadsProviderProps, type ChatTodo, type ChatTodoListProps, type CoralTaskType, type CreateAgentHandlerOptions, DEFAULT_TASK_MESSAGES, type ErrorCategory, type ModelRequest, type ModelStreamEvent, type NormalizedChatError, REQUEST_APPROVAL_TOOL, type ToolCall, type ToolResult, type ToolSpec, UPDATE_TODOS_TOOL, createAgentHandler, createHandler, createMemoryThreadsAdapter, fileToBase64, normalizeChatError, resolveTaskMessages, useChatContext, useChatThreads };
|
|
704
|
+
export { type AgentHandler, type AgentMessage, type AgentSessionStore, type AgentSnapshot, type AgentTool, type ApprovalOption, type CallModel, Chat, type ChatApprovalProps, type ChatApprovalRequest, type ChatAttachButtonProps, type ChatAttachType, type ChatAttachment, type ChatCanvasProps, type ChatChunk, type ChatComponents, type ChatErrorProps, type ChatInputProps, type ChatMessage, type ChatMessageRendererProps, type ChatMessagesProps, type ChatResponse, type ChatThread, type ChatThreadsAdapter, type ChatThreadsContextValue, ChatThreadsProvider, type ChatThreadsProviderProps, type ChatTodo, type ChatTodoListProps, type CoralTaskType, type CreateAgentHandlerOptions, DEFAULT_TASK_MESSAGES, type ErrorCategory, type ModelRequest, type ModelStreamEvent, type NormalizedChatError, RENDER_CHART_TOOL, REQUEST_APPROVAL_TOOL, type ToolCall, type ToolResult, type ToolSpec, UPDATE_TODOS_TOOL, createAgentHandler, createHandler, createMemoryThreadsAdapter, fileToBase64, normalizeChatError, resolveTaskMessages, useChatContext, useChatThreads };
|
package/dist/index.js
CHANGED
|
@@ -424,31 +424,54 @@ var ChatCanvas = ({
|
|
|
424
424
|
const response = await resolvedHandler(textToSend, historyForAI, snapshotAttachments.map((a) => a.file));
|
|
425
425
|
if (isAsyncIterable(response)) {
|
|
426
426
|
let assistantContent = "";
|
|
427
|
+
const blocks = [];
|
|
428
|
+
let openText = "";
|
|
429
|
+
let todosIndex = null;
|
|
427
430
|
let rafId = null;
|
|
428
|
-
let pendingContent = "";
|
|
429
431
|
let streamedApproval;
|
|
430
|
-
|
|
432
|
+
const snapshotBlocks = () => openText ? [...blocks, { type: "text", content: openText }] : blocks;
|
|
433
|
+
const flushOpenText = () => {
|
|
434
|
+
if (openText) {
|
|
435
|
+
blocks.push({ type: "text", content: openText });
|
|
436
|
+
openText = "";
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const commit = () => {
|
|
440
|
+
const nextBlocks = snapshotBlocks();
|
|
441
|
+
let nextMsg = {
|
|
442
|
+
id: assistantId,
|
|
443
|
+
role: "assistant",
|
|
444
|
+
content: assistantContent,
|
|
445
|
+
blocks: nextBlocks
|
|
446
|
+
};
|
|
447
|
+
setMessages((prev) => {
|
|
448
|
+
const updated = [...prev];
|
|
449
|
+
const last = updated[updated.length - 1];
|
|
450
|
+
if (last && last.id === assistantId) {
|
|
451
|
+
nextMsg = {
|
|
452
|
+
...last,
|
|
453
|
+
content: assistantContent,
|
|
454
|
+
blocks: nextBlocks
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
updated[updated.length - 1] = nextMsg;
|
|
458
|
+
return updated;
|
|
459
|
+
});
|
|
460
|
+
onMessagesChange?.(
|
|
461
|
+
[...updatedMessages, nextMsg],
|
|
462
|
+
sessionAtSend
|
|
463
|
+
);
|
|
464
|
+
};
|
|
431
465
|
for await (const chunk of response) {
|
|
432
466
|
if (shouldStopRef.current) break;
|
|
433
467
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) break;
|
|
434
468
|
if (typeof chunk === "string") {
|
|
435
469
|
assistantContent += chunk;
|
|
436
|
-
|
|
470
|
+
openText += chunk;
|
|
437
471
|
if (rafId === null) {
|
|
438
472
|
rafId = requestAnimationFrame(() => {
|
|
439
|
-
const content = pendingContent;
|
|
440
|
-
let nextMsg = { id: assistantId, role: "assistant", content };
|
|
441
|
-
setMessages((prev) => {
|
|
442
|
-
const updated = [...prev];
|
|
443
|
-
const last = updated[updated.length - 1];
|
|
444
|
-
if (last && last.id === assistantId) {
|
|
445
|
-
nextMsg = { ...last, content };
|
|
446
|
-
}
|
|
447
|
-
updated[updated.length - 1] = nextMsg;
|
|
448
|
-
return updated;
|
|
449
|
-
});
|
|
450
|
-
onMessagesChange?.([...updatedMessages, nextMsg], sessionAtSend);
|
|
451
473
|
rafId = null;
|
|
474
|
+
commit();
|
|
452
475
|
});
|
|
453
476
|
}
|
|
454
477
|
} else if (chunk.type === "reasoning") {
|
|
@@ -467,15 +490,18 @@ var ChatCanvas = ({
|
|
|
467
490
|
return updated;
|
|
468
491
|
});
|
|
469
492
|
} else if (chunk.type === "todos") {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
493
|
+
if (todosIndex === null) {
|
|
494
|
+
flushOpenText();
|
|
495
|
+
todosIndex = blocks.length;
|
|
496
|
+
blocks.push({ type: "todos", todos: chunk.todos });
|
|
497
|
+
} else {
|
|
498
|
+
blocks[todosIndex] = { type: "todos", todos: chunk.todos };
|
|
499
|
+
}
|
|
500
|
+
commit();
|
|
501
|
+
} else if (chunk.type === "chart") {
|
|
502
|
+
flushOpenText();
|
|
503
|
+
blocks.push({ type: "chart", spec: chunk.spec });
|
|
504
|
+
commit();
|
|
479
505
|
}
|
|
480
506
|
}
|
|
481
507
|
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
@@ -483,13 +509,14 @@ var ChatCanvas = ({
|
|
|
483
509
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) {
|
|
484
510
|
break;
|
|
485
511
|
}
|
|
512
|
+
flushOpenText();
|
|
486
513
|
const finalMsg = {
|
|
487
514
|
id: assistantId,
|
|
488
515
|
role: "assistant",
|
|
489
516
|
content: assistantContent,
|
|
517
|
+
blocks,
|
|
490
518
|
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {},
|
|
491
|
-
...streamedApproval ? { approval: streamedApproval } : {}
|
|
492
|
-
...streamedTodos ? { todos: streamedTodos } : {}
|
|
519
|
+
...streamedApproval ? { approval: streamedApproval } : {}
|
|
493
520
|
};
|
|
494
521
|
const finalMessages = [...updatedMessages, finalMsg];
|
|
495
522
|
setMessages(finalMessages);
|
|
@@ -1041,13 +1068,13 @@ import {
|
|
|
1041
1068
|
Body1 as Body14,
|
|
1042
1069
|
Caption1,
|
|
1043
1070
|
makeStyles as makeStyles6,
|
|
1044
|
-
Spinner as Spinner2,
|
|
1045
1071
|
tokens as tokens6
|
|
1046
1072
|
} from "@fluentui/react-components";
|
|
1047
1073
|
import {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1074
|
+
CheckboxCheckedFilled,
|
|
1075
|
+
CheckboxUncheckedRegular,
|
|
1076
|
+
ChevronRightRegular,
|
|
1077
|
+
SquareHintRegular
|
|
1051
1078
|
} from "@fluentui/react-icons";
|
|
1052
1079
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1053
1080
|
var useStyles6 = makeStyles6({
|
|
@@ -1084,6 +1111,7 @@ var useStyles6 = makeStyles6({
|
|
|
1084
1111
|
chevron: {
|
|
1085
1112
|
color: tokens6.colorNeutralForeground3,
|
|
1086
1113
|
flexShrink: 0,
|
|
1114
|
+
fontSize: "16px",
|
|
1087
1115
|
transition: "transform 0.2s ease"
|
|
1088
1116
|
},
|
|
1089
1117
|
chevronExpanded: {
|
|
@@ -1104,15 +1132,19 @@ var useStyles6 = makeStyles6({
|
|
|
1104
1132
|
display: "flex",
|
|
1105
1133
|
alignItems: "center",
|
|
1106
1134
|
justifyContent: "center",
|
|
1107
|
-
width: "
|
|
1135
|
+
width: "20px",
|
|
1108
1136
|
height: "20px",
|
|
1137
|
+
fontSize: "20px",
|
|
1109
1138
|
flexShrink: 0
|
|
1110
1139
|
},
|
|
1111
1140
|
pendingIcon: {
|
|
1112
|
-
color: tokens6.
|
|
1141
|
+
color: tokens6.colorNeutralForegroundDisabled
|
|
1142
|
+
},
|
|
1143
|
+
activeIcon: {
|
|
1144
|
+
color: tokens6.colorNeutralForeground2
|
|
1113
1145
|
},
|
|
1114
1146
|
doneIcon: {
|
|
1115
|
-
color: tokens6.
|
|
1147
|
+
color: tokens6.colorPaletteSeafoamForeground2
|
|
1116
1148
|
},
|
|
1117
1149
|
text: {
|
|
1118
1150
|
minWidth: 0,
|
|
@@ -1157,7 +1189,7 @@ var ChatTodoList = ({
|
|
|
1157
1189
|
"aria-expanded": expanded,
|
|
1158
1190
|
children: [
|
|
1159
1191
|
/* @__PURE__ */ jsx6(
|
|
1160
|
-
|
|
1192
|
+
ChevronRightRegular,
|
|
1161
1193
|
{
|
|
1162
1194
|
className: `${styles.chevron}${expanded ? ` ${styles.chevronExpanded}` : ""}`
|
|
1163
1195
|
}
|
|
@@ -1180,7 +1212,7 @@ var ChatTodoList = ({
|
|
|
1180
1212
|
className: styles.item,
|
|
1181
1213
|
role: "listitem",
|
|
1182
1214
|
children: [
|
|
1183
|
-
/* @__PURE__ */ jsx6("span", { className: styles.iconCell, children: todo.status === "completed" ? /* @__PURE__ */ jsx6(
|
|
1215
|
+
/* @__PURE__ */ jsx6("span", { className: styles.iconCell, children: todo.status === "completed" ? /* @__PURE__ */ jsx6(CheckboxCheckedFilled, { className: styles.doneIcon }) : todo.status === "in_progress" ? /* @__PURE__ */ jsx6(CheckboxUncheckedRegular, { className: styles.activeIcon }) : /* @__PURE__ */ jsx6(SquareHintRegular, { className: styles.pendingIcon }) }),
|
|
1184
1216
|
/* @__PURE__ */ jsx6(Body14, { className: `${styles.text} ${textClass}`, children: text })
|
|
1185
1217
|
]
|
|
1186
1218
|
},
|
|
@@ -1194,8 +1226,21 @@ var ChatTodoList = ({
|
|
|
1194
1226
|
var Chat_TodoList_default = ChatTodoList;
|
|
1195
1227
|
|
|
1196
1228
|
// src/Chat.Messages.tsx
|
|
1229
|
+
import { CoralChart } from "@coral-ai/charts";
|
|
1197
1230
|
import { CheckmarkCircle20Filled } from "@fluentui/react-icons";
|
|
1198
1231
|
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1232
|
+
function resolveBlocks(msg) {
|
|
1233
|
+
if (msg.blocks) return msg.blocks;
|
|
1234
|
+
const legacy = [];
|
|
1235
|
+
if (msg.todos && msg.todos.length > 0) {
|
|
1236
|
+
legacy.push({ type: "todos", todos: msg.todos });
|
|
1237
|
+
}
|
|
1238
|
+
if (msg.charts) {
|
|
1239
|
+
for (const spec of msg.charts) legacy.push({ type: "chart", spec });
|
|
1240
|
+
}
|
|
1241
|
+
if (msg.content) legacy.push({ type: "text", content: msg.content });
|
|
1242
|
+
return legacy;
|
|
1243
|
+
}
|
|
1199
1244
|
var useStyles7 = makeStyles7({
|
|
1200
1245
|
messages: {
|
|
1201
1246
|
flex: "1",
|
|
@@ -1253,7 +1298,7 @@ var useStyles7 = makeStyles7({
|
|
|
1253
1298
|
display: "flex",
|
|
1254
1299
|
flexDirection: "column",
|
|
1255
1300
|
alignItems: "stretch",
|
|
1256
|
-
width: "min(640px, 80vw)",
|
|
1301
|
+
// width: "min(640px, 80vw)",
|
|
1257
1302
|
gap: "8px"
|
|
1258
1303
|
},
|
|
1259
1304
|
userEditTextarea: {
|
|
@@ -1559,16 +1604,24 @@ var ChatMessages = ({
|
|
|
1559
1604
|
isStreaming: isLatest && isTyping && !msg.content
|
|
1560
1605
|
}
|
|
1561
1606
|
),
|
|
1562
|
-
msg.
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
{
|
|
1566
|
-
onNavigate,
|
|
1567
|
-
fontSize,
|
|
1568
|
-
fontFamily,
|
|
1569
|
-
children: msg.content
|
|
1607
|
+
resolveBlocks(msg).map((block, i) => {
|
|
1608
|
+
if (block.type === "todos") {
|
|
1609
|
+
return /* @__PURE__ */ jsx7(Chat_TodoList_default, { todos: block.todos }, `todos-${i}`);
|
|
1570
1610
|
}
|
|
1571
|
-
|
|
1611
|
+
if (block.type === "chart") {
|
|
1612
|
+
return /* @__PURE__ */ jsx7(CoralChart, { spec: block.spec }, `chart-${i}`);
|
|
1613
|
+
}
|
|
1614
|
+
return /* @__PURE__ */ jsx7(
|
|
1615
|
+
CoralMarkdown,
|
|
1616
|
+
{
|
|
1617
|
+
onNavigate,
|
|
1618
|
+
fontSize,
|
|
1619
|
+
fontFamily,
|
|
1620
|
+
children: block.content
|
|
1621
|
+
},
|
|
1622
|
+
`text-${i}`
|
|
1623
|
+
);
|
|
1624
|
+
}),
|
|
1572
1625
|
msg.error && /* @__PURE__ */ jsx7(
|
|
1573
1626
|
Chat_Error_default,
|
|
1574
1627
|
{
|
|
@@ -2457,6 +2510,49 @@ var REQUEST_APPROVAL_TOOL = {
|
|
|
2457
2510
|
required: ["question", "options"]
|
|
2458
2511
|
}
|
|
2459
2512
|
};
|
|
2513
|
+
var RENDER_CHART_TOOL = {
|
|
2514
|
+
name: "render_chart",
|
|
2515
|
+
description: 'Render a chart inline in the assistant message. Call this when a visualization (bar or line) communicates the data more clearly than prose or a table. The chart appears above your text \u2014 refer to it naturally ("as the chart above shows\u2026") and do NOT also produce ASCII charts or code blocks for the same data. May be called multiple times in a turn to render multiple charts.',
|
|
2516
|
+
parameters: {
|
|
2517
|
+
type: "object",
|
|
2518
|
+
properties: {
|
|
2519
|
+
kind: {
|
|
2520
|
+
type: "string",
|
|
2521
|
+
enum: ["bar", "line"],
|
|
2522
|
+
description: "Use 'bar' for categorical comparisons, 'line' for trends over an ordered axis."
|
|
2523
|
+
},
|
|
2524
|
+
title: { type: "string", description: "Title shown above the chart." },
|
|
2525
|
+
xKey: {
|
|
2526
|
+
type: "string",
|
|
2527
|
+
description: "Key in each `data` row that supplies the x-axis label."
|
|
2528
|
+
},
|
|
2529
|
+
series: {
|
|
2530
|
+
type: "array",
|
|
2531
|
+
description: "One entry per data series (one bar group / one line).",
|
|
2532
|
+
items: {
|
|
2533
|
+
type: "object",
|
|
2534
|
+
properties: {
|
|
2535
|
+
dataKey: {
|
|
2536
|
+
type: "string",
|
|
2537
|
+
description: "Key in each `data` row holding this series' numeric value."
|
|
2538
|
+
},
|
|
2539
|
+
label: {
|
|
2540
|
+
type: "string",
|
|
2541
|
+
description: "Display label shown in the legend and tooltip."
|
|
2542
|
+
}
|
|
2543
|
+
},
|
|
2544
|
+
required: ["dataKey", "label"]
|
|
2545
|
+
}
|
|
2546
|
+
},
|
|
2547
|
+
data: {
|
|
2548
|
+
type: "array",
|
|
2549
|
+
description: "Rows of data containing the xKey and every series' dataKey. Example: `[{ quarter: 'Q1', alice: 92, bob: 78 }, \u2026]`.",
|
|
2550
|
+
items: { type: "object" }
|
|
2551
|
+
}
|
|
2552
|
+
},
|
|
2553
|
+
required: ["kind", "title", "xKey", "series", "data"]
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2460
2556
|
function createMemoryStore() {
|
|
2461
2557
|
const map = /* @__PURE__ */ new Map();
|
|
2462
2558
|
return {
|
|
@@ -2490,6 +2586,24 @@ function extractTodos(input) {
|
|
|
2490
2586
|
};
|
|
2491
2587
|
});
|
|
2492
2588
|
}
|
|
2589
|
+
function buildChartSpec(input) {
|
|
2590
|
+
const o = input ?? {};
|
|
2591
|
+
const kind = o.kind === "line" ? "line" : "bar";
|
|
2592
|
+
const title = typeof o.title === "string" ? o.title : "";
|
|
2593
|
+
const xKey = typeof o.xKey === "string" ? o.xKey : "";
|
|
2594
|
+
const rawSeries = Array.isArray(o.series) ? o.series : [];
|
|
2595
|
+
const series = rawSeries.map((s, i) => {
|
|
2596
|
+
const obj = s ?? {};
|
|
2597
|
+
const dataKey = typeof obj.dataKey === "string" ? obj.dataKey : "";
|
|
2598
|
+
const label = typeof obj.label === "string" ? obj.label : dataKey || `series-${i}`;
|
|
2599
|
+
return dataKey ? { dataKey, label } : null;
|
|
2600
|
+
}).filter((s) => s !== null);
|
|
2601
|
+
const data = Array.isArray(o.data) ? o.data.filter(
|
|
2602
|
+
(r) => r && typeof r === "object"
|
|
2603
|
+
) : [];
|
|
2604
|
+
if (!xKey || series.length === 0 || data.length === 0) return null;
|
|
2605
|
+
return { kind, title, xKey, series, data };
|
|
2606
|
+
}
|
|
2493
2607
|
function buildApproval(tc) {
|
|
2494
2608
|
const input = tc.input ?? {};
|
|
2495
2609
|
const rawOptions = Array.isArray(input.options) ? input.options : [];
|
|
@@ -2518,6 +2632,7 @@ function createAgentHandler(options) {
|
|
|
2518
2632
|
const toolSpecs = [
|
|
2519
2633
|
UPDATE_TODOS_TOOL,
|
|
2520
2634
|
REQUEST_APPROVAL_TOOL,
|
|
2635
|
+
RENDER_CHART_TOOL,
|
|
2521
2636
|
...tools.map((t) => t.spec)
|
|
2522
2637
|
];
|
|
2523
2638
|
return async function* (_input, history, attachments) {
|
|
@@ -2585,6 +2700,22 @@ function createAgentHandler(options) {
|
|
|
2585
2700
|
yield { type: "approval", approval: buildApproval(tc) };
|
|
2586
2701
|
await store.set(tc.id, { messages, pendingToolCallId: tc.id });
|
|
2587
2702
|
return;
|
|
2703
|
+
} else if (tc.name === RENDER_CHART_TOOL.name) {
|
|
2704
|
+
const spec = buildChartSpec(tc.input);
|
|
2705
|
+
if (spec) {
|
|
2706
|
+
yield { type: "chart", spec };
|
|
2707
|
+
messages.push({
|
|
2708
|
+
role: "tool",
|
|
2709
|
+
toolCallId: tc.id,
|
|
2710
|
+
content: "Chart rendered and shown to the user above your text."
|
|
2711
|
+
});
|
|
2712
|
+
} else {
|
|
2713
|
+
messages.push({
|
|
2714
|
+
role: "tool",
|
|
2715
|
+
toolCallId: tc.id,
|
|
2716
|
+
content: "Error: chart spec was invalid. Need non-empty `xKey`, at least one entry in `series`, and at least one row in `data`."
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2588
2719
|
} else {
|
|
2589
2720
|
const tool = customByName.get(tc.name);
|
|
2590
2721
|
let content;
|
|
@@ -2781,6 +2912,7 @@ export {
|
|
|
2781
2912
|
Chat2 as Chat,
|
|
2782
2913
|
ChatThreadsProvider,
|
|
2783
2914
|
DEFAULT_TASK_MESSAGES,
|
|
2915
|
+
RENDER_CHART_TOOL,
|
|
2784
2916
|
REQUEST_APPROVAL_TOOL,
|
|
2785
2917
|
UPDATE_TODOS_TOOL,
|
|
2786
2918
|
createAgentHandler,
|