yamchart 0.8.7 → 0.9.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/{advisor-54JBE2EV.js → advisor-Z7TKPPBR.js} +10 -10
- package/dist/agent-KWKPAYT2.js +8 -0
- package/dist/{chunk-NCPWAWIM.js → chunk-AMHCOB4D.js} +4 -4
- package/dist/{chunk-5N3FYFBV.js → chunk-CWAWATL4.js} +34 -7
- package/dist/chunk-CWAWATL4.js.map +1 -0
- package/dist/{chunk-C7A7TKSY.js → chunk-E2QN2M7S.js} +77 -7
- package/dist/chunk-E2QN2M7S.js.map +1 -0
- package/dist/{chunk-YMQ4PWVJ.js → chunk-FZFBBB7K.js} +2 -2
- package/dist/{chunk-GLCEDWGH.js → chunk-G57J2WQM.js} +4 -4
- package/dist/chunk-ZA6AOQVZ.js +677 -0
- package/dist/chunk-ZA6AOQVZ.js.map +1 -0
- package/dist/{connection-utils-MXEF6X7K.js → connection-utils-CTPN7PV3.js} +4 -4
- package/dist/{describe-XKLBZEWG.js → describe-4NME6RCB.js} +5 -5
- package/dist/{dev-Z2R2DBWO.js → dev-6QGAB4ZH.js} +845 -70
- package/dist/dev-6QGAB4ZH.js.map +1 -0
- package/dist/{dist-LJR7TAW4.js → dist-4GUE24QV.js} +4 -2
- package/dist/{dist-GVNWQXFR.js → dist-7CRX2GIR.js} +2 -2
- package/dist/{dist-E2PVGIPT.js → dist-VNX77VV5.js} +4 -2
- package/dist/index.js +21 -21
- package/dist/public/assets/{EventManagement-DtPDwZ-w.js → EventManagement-MMsAkJKj.js} +2 -2
- package/dist/public/assets/ExplorePage-BSkSNgLT.js +1 -0
- package/dist/public/assets/{LoginPage-DBq1qDOK.js → LoginPage-vaI1dnyL.js} +1 -1
- package/dist/public/assets/PublicViewer-B-OKj2cg.js +1 -0
- package/dist/public/assets/{SetupWizard-hgd12cdr.js → SetupWizard-DvlVX2O6.js} +1 -1
- package/dist/public/assets/{ShareManagement-D82oEJJg.js → ShareManagement-ulvPrOAQ.js} +1 -1
- package/dist/public/assets/{UserManagement-CZsxY9aP.js → UserManagement-CvmpNy3o.js} +1 -1
- package/dist/public/assets/{index-B_fusLA_.css → index-CfyF2Wf-.css} +1 -1
- package/dist/public/assets/index-DD59fsOk.js +195 -0
- package/dist/public/assets/{index.es-BmKO-vE1.js → index.es-BeTaRWIv.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-DMVrmE3G.js → jspdf.es.min-9haD1GSE.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-MXMFI5TB.js → query-Z75RKTHV.js} +4 -4
- package/dist/{sample-HDPYNAKS.js → sample-OIJNXQNC.js} +4 -4
- package/dist/{search-6CPEPJTI.js → search-YDCPIDZX.js} +5 -5
- package/dist/{source-resolver-PCASPRSD.js → source-resolver-4SUWXUGW.js} +5 -5
- package/dist/source-resolver-4SUWXUGW.js.map +1 -0
- package/dist/{sync-warehouse-XC7YYZKC.js → sync-warehouse-NZFDS6WK.js} +4 -4
- package/dist/{tables-26PNVZIC.js → tables-WJS2VI4L.js} +5 -5
- package/dist/templates/default/CLAUDE.md +1 -1
- package/dist/templates/default/docs/yamchart-reference.md +164 -2
- package/dist/templates/default/yamchart.yaml +3 -0
- package/dist/{test-APA44AIF.js → test-I4XOF7TZ.js} +5 -17
- package/dist/test-I4XOF7TZ.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-5N3FYFBV.js.map +0 -1
- package/dist/chunk-C7A7TKSY.js.map +0 -1
- package/dist/dev-Z2R2DBWO.js.map +0 -1
- package/dist/public/assets/PublicViewer-Da8Cu1C1.js +0 -1
- package/dist/public/assets/index-DVSm0iiw.js +0 -187
- package/dist/test-APA44AIF.js.map +0 -1
- /package/dist/{advisor-54JBE2EV.js.map → advisor-Z7TKPPBR.js.map} +0 -0
- /package/dist/{connection-utils-MXEF6X7K.js.map → agent-KWKPAYT2.js.map} +0 -0
- /package/dist/{chunk-NCPWAWIM.js.map → chunk-AMHCOB4D.js.map} +0 -0
- /package/dist/{chunk-YMQ4PWVJ.js.map → chunk-FZFBBB7K.js.map} +0 -0
- /package/dist/{chunk-GLCEDWGH.js.map → chunk-G57J2WQM.js.map} +0 -0
- /package/dist/{dist-E2PVGIPT.js.map → connection-utils-CTPN7PV3.js.map} +0 -0
- /package/dist/{describe-XKLBZEWG.js.map → describe-4NME6RCB.js.map} +0 -0
- /package/dist/{dist-LJR7TAW4.js.map → dist-4GUE24QV.js.map} +0 -0
- /package/dist/{dist-GVNWQXFR.js.map → dist-7CRX2GIR.js.map} +0 -0
- /package/dist/{source-resolver-PCASPRSD.js.map → dist-VNX77VV5.js.map} +0 -0
- /package/dist/{query-MXMFI5TB.js.map → query-Z75RKTHV.js.map} +0 -0
- /package/dist/{sample-HDPYNAKS.js.map → sample-OIJNXQNC.js.map} +0 -0
- /package/dist/{search-6CPEPJTI.js.map → search-YDCPIDZX.js.map} +0 -0
- /package/dist/{sync-warehouse-XC7YYZKC.js.map → sync-warehouse-NZFDS6WK.js.map} +0 -0
- /package/dist/{tables-26PNVZIC.js.map → tables-WJS2VI4L.js.map} +0 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
// ../../packages/chat/dist/agent.js
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
|
|
4
|
+
// ../../packages/chat/dist/tools.js
|
|
5
|
+
import yaml from "yaml";
|
|
6
|
+
|
|
7
|
+
// ../../packages/chat/dist/catalog-filter.js
|
|
8
|
+
function extractSchema(table) {
|
|
9
|
+
const parts = table.split(".");
|
|
10
|
+
if (parts.length >= 3)
|
|
11
|
+
return parts[parts.length - 2] ?? null;
|
|
12
|
+
if (parts.length === 2)
|
|
13
|
+
return parts[0] ?? null;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function filterCatalogEntries(entries, filterConfig) {
|
|
17
|
+
if (!filterConfig)
|
|
18
|
+
return [];
|
|
19
|
+
const schemas = filterConfig.schemas ?? [];
|
|
20
|
+
const tags = filterConfig.tags ?? [];
|
|
21
|
+
if (schemas.length === 0 && tags.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
const schemasLower = schemas.map((s) => s.toLowerCase());
|
|
24
|
+
const tagsLower = tags.map((t) => t.toLowerCase());
|
|
25
|
+
return entries.filter((entry) => {
|
|
26
|
+
if (schemasLower.length > 0 && entry.table) {
|
|
27
|
+
const schema = extractSchema(entry.table);
|
|
28
|
+
if (schema && schemasLower.includes(schema.toLowerCase()))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (tagsLower.length > 0 && entry.tags?.length) {
|
|
32
|
+
if (entry.tags.some((t) => tagsLower.includes(t.toLowerCase())))
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ../../packages/chat/dist/tools.js
|
|
40
|
+
var CHAT_TOOL_DEFINITIONS = [
|
|
41
|
+
{
|
|
42
|
+
name: "query_model",
|
|
43
|
+
description: 'Execute a chart or SQL model by name with optional parameters. Returns rows and columns. Accepts either a chart name (e.g. "social-traffic") or a model name (e.g. "revenue_over_time").',
|
|
44
|
+
parameters: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
model: { type: "string", description: 'Chart name or model name (e.g. "social-traffic" or "revenue_over_time")' },
|
|
48
|
+
params: { type: "object", description: "Optional parameters to pass to the model" },
|
|
49
|
+
limit: { type: "number", description: "Max rows to return (default: 100)" }
|
|
50
|
+
},
|
|
51
|
+
required: ["model"]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "get_chart_config",
|
|
56
|
+
description: "Get the full YAML configuration of a chart by name.",
|
|
57
|
+
parameters: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: { name: { type: "string", description: "Chart name" } },
|
|
60
|
+
required: ["name"]
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "get_dashboard_summary",
|
|
65
|
+
description: "Get a summary of a dashboard: all charts, their types, and cached KPI values.",
|
|
66
|
+
parameters: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: { name: { type: "string", description: "Dashboard name" } },
|
|
69
|
+
required: ["name"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "apply_filter",
|
|
74
|
+
description: "Apply a filter to the dashboard the user is viewing. Updates UI in real-time.",
|
|
75
|
+
parameters: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
name: { type: "string", description: "Filter/parameter name" },
|
|
79
|
+
value: { description: "Filter value" }
|
|
80
|
+
},
|
|
81
|
+
required: ["name", "value"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "run_sql",
|
|
86
|
+
description: "Execute an ad-hoc SQL query against the connected database.",
|
|
87
|
+
parameters: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
sql: { type: "string", description: "SQL query to execute" },
|
|
91
|
+
limit: { type: "number", description: "Max rows (default: 50)" }
|
|
92
|
+
},
|
|
93
|
+
required: ["sql"]
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "compare_periods",
|
|
98
|
+
description: "Run a model with two different date ranges and return both result sets for comparison.",
|
|
99
|
+
parameters: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
model: { type: "string", description: "Model name" },
|
|
103
|
+
current: { type: "object", properties: { start_date: { type: "string" }, end_date: { type: "string" } }, required: ["start_date", "end_date"] },
|
|
104
|
+
previous: { type: "object", properties: { start_date: { type: "string" }, end_date: { type: "string" } }, required: ["start_date", "end_date"] },
|
|
105
|
+
params: { type: "object", description: "Additional parameters" }
|
|
106
|
+
},
|
|
107
|
+
required: ["model", "current", "previous"]
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "get_catalog_tables",
|
|
112
|
+
description: "Search the warehouse catalog for tables matching a keyword. Returns table names, columns, and sample rows.",
|
|
113
|
+
parameters: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
query: { type: "string", description: "Keyword to search for in table/column names" },
|
|
117
|
+
limit: { type: "number", description: "Max tables to return (default: 5)" }
|
|
118
|
+
},
|
|
119
|
+
required: ["query"]
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "suggest_actions",
|
|
124
|
+
description: "Show the user clickable suggested follow-up actions. Use after answering a question when there are natural next steps.",
|
|
125
|
+
parameters: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
suggestions: {
|
|
129
|
+
type: "array",
|
|
130
|
+
items: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
label: { type: "string", description: "Button text" },
|
|
134
|
+
message: { type: "string", description: "Message to send when clicked" },
|
|
135
|
+
type: { type: "string", enum: ["explore", "save"], description: "explore for follow-up questions, save for creating artifacts" }
|
|
136
|
+
},
|
|
137
|
+
required: ["label", "message", "type"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ["suggestions"]
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "save_model",
|
|
146
|
+
description: "Create a new SQL model file in the project. Writes to models/<name>.sql with metadata headers.",
|
|
147
|
+
parameters: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
name: { type: "string", description: "Model name (alphanumeric + underscores)" },
|
|
151
|
+
sql: { type: "string", description: "SQL query body" },
|
|
152
|
+
description: { type: "string", description: "Model description" },
|
|
153
|
+
params: {
|
|
154
|
+
type: "array",
|
|
155
|
+
items: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
name: { type: "string" },
|
|
159
|
+
type: { type: "string" },
|
|
160
|
+
default: { type: "string" }
|
|
161
|
+
},
|
|
162
|
+
required: ["name", "type"]
|
|
163
|
+
},
|
|
164
|
+
description: "Model parameters"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
required: ["name", "sql"]
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "save_chart",
|
|
172
|
+
description: "Create a new chart YAML file in the project. Writes to charts/<name>.yaml.",
|
|
173
|
+
parameters: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
name: { type: "string", description: "Chart name (alphanumeric, hyphens, underscores)" },
|
|
177
|
+
title: { type: "string", description: "Display title" },
|
|
178
|
+
model: { type: "string", description: "Source model name" },
|
|
179
|
+
chart_type: { type: "string", description: "Chart type (line, bar, area, pie, donut, scatter, kpi, combo, heatmap, funnel, waterfall, gauge, sankey, table)" },
|
|
180
|
+
config: { type: "object", description: "Chart-type-specific configuration (x, y, series, etc.)" }
|
|
181
|
+
},
|
|
182
|
+
required: ["name", "title", "model", "chart_type", "config"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "add_to_dashboard",
|
|
187
|
+
description: "Add a chart widget to an existing dashboard layout.",
|
|
188
|
+
parameters: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
dashboard: { type: "string", description: "Dashboard name" },
|
|
192
|
+
chart: { type: "string", description: "Chart name to add" },
|
|
193
|
+
cols: { type: "number", description: "Column span (1-12, default: 6)" },
|
|
194
|
+
height: { type: "number", description: "Row height in pixels (default: 300)" },
|
|
195
|
+
tab: { type: "string", description: "Tab name to add to (if dashboard uses tabs)" }
|
|
196
|
+
},
|
|
197
|
+
required: ["dashboard", "chart"]
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "create_dashboard",
|
|
202
|
+
description: "Create a new dashboard YAML file with layout referencing saved charts.",
|
|
203
|
+
parameters: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
name: { type: "string", description: 'Dashboard name (kebab-case, e.g. "sales-overview")' },
|
|
207
|
+
title: { type: "string", description: "Display title" },
|
|
208
|
+
description: { type: "string", description: "Optional dashboard description" },
|
|
209
|
+
charts: {
|
|
210
|
+
type: "array",
|
|
211
|
+
items: { type: "string" },
|
|
212
|
+
description: "List of chart names to include in the dashboard layout"
|
|
213
|
+
},
|
|
214
|
+
layout: {
|
|
215
|
+
type: "string",
|
|
216
|
+
enum: ["auto"],
|
|
217
|
+
description: 'Layout mode (only "auto" supported for now)'
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
required: ["name", "title", "charts"]
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "call_agent",
|
|
225
|
+
description: "Delegate a question to a specialized agent. Use when the question is better handled by a domain-specific agent.",
|
|
226
|
+
parameters: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
agent: { type: "string", description: "Agent name to delegate to" },
|
|
230
|
+
question: { type: "string", description: "Question to ask the agent" }
|
|
231
|
+
},
|
|
232
|
+
required: ["agent", "question"]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
];
|
|
236
|
+
async function executeChatTool(toolName, args, ctx) {
|
|
237
|
+
switch (toolName) {
|
|
238
|
+
case "query_model": {
|
|
239
|
+
const modelName = args.model;
|
|
240
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.\-]*$/.test(modelName)) {
|
|
241
|
+
return `Error: Invalid model name "${modelName}"`;
|
|
242
|
+
}
|
|
243
|
+
const charts = ctx.configLoader.getCharts();
|
|
244
|
+
const chart = charts.find((c) => c.name === modelName) ?? charts.find((c) => c.source?.model === modelName);
|
|
245
|
+
if (!chart) {
|
|
246
|
+
try {
|
|
247
|
+
const result2 = await ctx.queryService.executeRawSql(`SELECT * FROM ${modelName} LIMIT ${args.limit ?? 100}`);
|
|
248
|
+
return JSON.stringify({ columns: result2.columns, rows: result2.rows.slice(0, args.limit ?? 100) });
|
|
249
|
+
} catch (err) {
|
|
250
|
+
return `Error: Model "${modelName}" not found as a chart source and raw query failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const params = args.params ?? {};
|
|
254
|
+
const result = await ctx.queryService.executeChart(chart, params, ctx.userContext);
|
|
255
|
+
const limit = args.limit ?? 100;
|
|
256
|
+
return JSON.stringify({ columns: result.columns, rows: result.rows.slice(0, limit) });
|
|
257
|
+
}
|
|
258
|
+
case "get_chart_config": {
|
|
259
|
+
const chart = ctx.configLoader.getChartByName(args.name);
|
|
260
|
+
if (!chart)
|
|
261
|
+
return `Error: Chart "${args.name}" not found`;
|
|
262
|
+
return JSON.stringify(chart);
|
|
263
|
+
}
|
|
264
|
+
case "get_dashboard_summary": {
|
|
265
|
+
const dashName = args.name ?? ctx.chatContext?.name;
|
|
266
|
+
if (!dashName)
|
|
267
|
+
return "Error: No dashboard name provided and no dashboard context available";
|
|
268
|
+
let dashboard = ctx.configLoader.getDashboardByName(dashName);
|
|
269
|
+
if (!dashboard) {
|
|
270
|
+
const all = ctx.configLoader.getDashboards();
|
|
271
|
+
dashboard = all.find((d) => d.title?.toLowerCase() === dashName.toLowerCase() || d.name.toLowerCase() === dashName.toLowerCase().replace(/\s+/g, "-"));
|
|
272
|
+
}
|
|
273
|
+
if (!dashboard)
|
|
274
|
+
return `Error: Dashboard "${dashName}" not found`;
|
|
275
|
+
const chartRefs = [];
|
|
276
|
+
const extractRefs = (layout) => {
|
|
277
|
+
for (const row of layout.rows ?? []) {
|
|
278
|
+
for (const widget of row.widgets ?? []) {
|
|
279
|
+
if (widget.type === "chart" && widget.ref)
|
|
280
|
+
chartRefs.push(widget.ref);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
if (dashboard.layout)
|
|
285
|
+
extractRefs(dashboard.layout);
|
|
286
|
+
if (dashboard.tabs) {
|
|
287
|
+
for (const tab of dashboard.tabs) {
|
|
288
|
+
if (tab.layout)
|
|
289
|
+
extractRefs(tab.layout);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const chartSummaries = [];
|
|
293
|
+
for (const ref of chartRefs) {
|
|
294
|
+
const chart = ctx.configLoader.getChartByName(ref);
|
|
295
|
+
if (!chart)
|
|
296
|
+
continue;
|
|
297
|
+
const model = chart.source?.model ? ` [model: ${chart.source.model}]` : "";
|
|
298
|
+
chartSummaries.push(`- ${ref} (${chart.chart?.type ?? "unknown"}): ${chart.title ?? ref}${model}`);
|
|
299
|
+
}
|
|
300
|
+
return [`Dashboard: ${dashboard.title ?? dashboard.name}`, `Charts (${chartRefs.length}):`, ...chartSummaries].join("\n");
|
|
301
|
+
}
|
|
302
|
+
case "apply_filter": {
|
|
303
|
+
const filterName = args.name;
|
|
304
|
+
const filterValue = args.value;
|
|
305
|
+
ctx.emitEvent({ type: "filter_update", data: { filters: { [filterName]: filterValue } } });
|
|
306
|
+
return `Filter applied: ${filterName} = ${JSON.stringify(filterValue)}`;
|
|
307
|
+
}
|
|
308
|
+
case "run_sql": {
|
|
309
|
+
const sql = args.sql;
|
|
310
|
+
const destructivePattern = /^\s*(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE)\b/i;
|
|
311
|
+
if (destructivePattern.test(sql)) {
|
|
312
|
+
return "Error: Destructive SQL statements are not allowed through the chat interface.";
|
|
313
|
+
}
|
|
314
|
+
const limit = args.limit ?? 50;
|
|
315
|
+
const limitedSql = sql.toLowerCase().includes("limit") ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${limit}`;
|
|
316
|
+
const result = await ctx.queryService.executeRawSql(limitedSql);
|
|
317
|
+
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
318
|
+
}
|
|
319
|
+
case "compare_periods": {
|
|
320
|
+
const modelName = args.model;
|
|
321
|
+
const current = args.current;
|
|
322
|
+
const previous = args.previous;
|
|
323
|
+
const extraParams = args.params ?? {};
|
|
324
|
+
const charts = ctx.configLoader.getCharts();
|
|
325
|
+
const chart = charts.find((c) => c.source?.model === modelName);
|
|
326
|
+
if (!chart)
|
|
327
|
+
return `Error: No chart found using model "${modelName}"`;
|
|
328
|
+
const [currentResult, previousResult] = await Promise.all([
|
|
329
|
+
ctx.queryService.executeChart(chart, { ...extraParams, start_date: current.start_date, end_date: current.end_date }, ctx.userContext),
|
|
330
|
+
ctx.queryService.executeChart(chart, { ...extraParams, start_date: previous.start_date, end_date: previous.end_date }, ctx.userContext)
|
|
331
|
+
]);
|
|
332
|
+
return JSON.stringify({
|
|
333
|
+
current: { period: current, rows: currentResult.rows, columns: currentResult.columns },
|
|
334
|
+
previous: { period: previous, rows: previousResult.rows, columns: previousResult.columns }
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
case "get_catalog_tables": {
|
|
338
|
+
if (!ctx.catalogPath) {
|
|
339
|
+
return "Error: No catalog available. Run `yamchart sync-warehouse` to sync warehouse metadata.";
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const fs = await import("fs/promises");
|
|
343
|
+
const raw = await fs.readFile(ctx.catalogPath, "utf-8");
|
|
344
|
+
const catalog = JSON.parse(raw);
|
|
345
|
+
const allEntries = catalog.models ?? [];
|
|
346
|
+
const entries = ctx.catalogFilter ? filterCatalogEntries(allEntries, ctx.catalogFilter) : allEntries;
|
|
347
|
+
if (entries.length === 0) {
|
|
348
|
+
if (allEntries.length > 0 && ctx.catalogFilter) {
|
|
349
|
+
return "No tables match your catalog filter configuration. Check catalog.include in yamchart.yaml to adjust which schemas/tags are included.";
|
|
350
|
+
}
|
|
351
|
+
return "No catalog data available. Run `yamchart sync-warehouse` to populate the catalog.";
|
|
352
|
+
}
|
|
353
|
+
const query = args.query.toLowerCase();
|
|
354
|
+
const limit = args.limit ?? 5;
|
|
355
|
+
const matches = entries.filter((t) => t.name.toLowerCase().includes(query) || (t.columns ?? []).some((c) => c.name.toLowerCase().includes(query)));
|
|
356
|
+
return JSON.stringify(matches.slice(0, limit));
|
|
357
|
+
} catch (err) {
|
|
358
|
+
return `Error reading catalog: ${err instanceof Error ? err.message : String(err)}`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
case "suggest_actions": {
|
|
362
|
+
const suggestions = args.suggestions;
|
|
363
|
+
ctx.emitEvent({ type: "suggestions", data: { suggestions } });
|
|
364
|
+
return `Suggested ${suggestions.length} follow-up actions.`;
|
|
365
|
+
}
|
|
366
|
+
case "save_model": {
|
|
367
|
+
const name = args.name;
|
|
368
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
369
|
+
return `Error: Invalid model name "${name}". Use only letters, numbers, and underscores.`;
|
|
370
|
+
}
|
|
371
|
+
const sql = args.sql;
|
|
372
|
+
const description = args.description;
|
|
373
|
+
const params = args.params;
|
|
374
|
+
const lines = [];
|
|
375
|
+
lines.push(`-- @name: ${name}`);
|
|
376
|
+
if (description)
|
|
377
|
+
lines.push(`-- @description: ${description}`);
|
|
378
|
+
if (params) {
|
|
379
|
+
for (const p of params) {
|
|
380
|
+
const def = p.default ? ` = ${p.default}` : "";
|
|
381
|
+
lines.push(`-- @param ${p.name}: ${p.type}${def}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lines.push("");
|
|
385
|
+
lines.push(sql);
|
|
386
|
+
const fs = await import("fs/promises");
|
|
387
|
+
const path = await import("path");
|
|
388
|
+
const modelsDir = path.join(ctx.projectDir, "models");
|
|
389
|
+
await fs.mkdir(modelsDir, { recursive: true });
|
|
390
|
+
const filePath = path.join(modelsDir, `${name}.sql`);
|
|
391
|
+
await fs.writeFile(filePath, lines.join("\n"), "utf-8");
|
|
392
|
+
await ctx.configLoader.load();
|
|
393
|
+
return `Model "${name}" saved to models/${name}.sql`;
|
|
394
|
+
}
|
|
395
|
+
case "save_chart": {
|
|
396
|
+
const name = args.name;
|
|
397
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_\-]*$/.test(name)) {
|
|
398
|
+
return `Error: Invalid chart name "${name}". Use only letters, numbers, hyphens, and underscores.`;
|
|
399
|
+
}
|
|
400
|
+
const title = args.title;
|
|
401
|
+
const model = args.model;
|
|
402
|
+
const chartType = args.chart_type;
|
|
403
|
+
const config = args.config ?? {};
|
|
404
|
+
const chartObj = {
|
|
405
|
+
name,
|
|
406
|
+
title,
|
|
407
|
+
source: { model },
|
|
408
|
+
chart: { type: chartType, ...config }
|
|
409
|
+
};
|
|
410
|
+
const fs = await import("fs/promises");
|
|
411
|
+
const path = await import("path");
|
|
412
|
+
const chartsDir = path.join(ctx.projectDir, "charts");
|
|
413
|
+
await fs.mkdir(chartsDir, { recursive: true });
|
|
414
|
+
const filePath = path.join(chartsDir, `${name}.yaml`);
|
|
415
|
+
await fs.writeFile(filePath, yaml.stringify(chartObj), "utf-8");
|
|
416
|
+
await ctx.configLoader.load();
|
|
417
|
+
ctx.emitEvent({ type: "chart_saved", data: { name } });
|
|
418
|
+
return `Chart "${name}" saved to charts/${name}.yaml`;
|
|
419
|
+
}
|
|
420
|
+
case "add_to_dashboard": {
|
|
421
|
+
const dashboardName = args.dashboard;
|
|
422
|
+
const chartName = args.chart;
|
|
423
|
+
const cols = args.cols ?? 6;
|
|
424
|
+
const height = args.height ?? 300;
|
|
425
|
+
const tabName = args.tab;
|
|
426
|
+
const fs = await import("fs/promises");
|
|
427
|
+
const path = await import("path");
|
|
428
|
+
const dashDir = path.join(ctx.projectDir, "dashboards");
|
|
429
|
+
let filePath;
|
|
430
|
+
try {
|
|
431
|
+
const files = await fs.readdir(dashDir);
|
|
432
|
+
const match = files.find((f) => {
|
|
433
|
+
const base = f.replace(/\.ya?ml$/, "");
|
|
434
|
+
return base === dashboardName;
|
|
435
|
+
});
|
|
436
|
+
if (match)
|
|
437
|
+
filePath = path.join(dashDir, match);
|
|
438
|
+
} catch {
|
|
439
|
+
return `Error: Dashboard directory not found at ${dashDir}`;
|
|
440
|
+
}
|
|
441
|
+
if (!filePath)
|
|
442
|
+
return `Error: Dashboard file "${dashboardName}" not found in dashboards/`;
|
|
443
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
444
|
+
const doc = yaml.parse(raw);
|
|
445
|
+
const newRow = { height, widgets: [{ type: "chart", ref: chartName, cols }] };
|
|
446
|
+
if (tabName && doc.tabs) {
|
|
447
|
+
const tab = doc.tabs.find((t) => t.name === tabName);
|
|
448
|
+
if (!tab)
|
|
449
|
+
return `Error: Tab "${tabName}" not found in dashboard "${dashboardName}"`;
|
|
450
|
+
if (!tab.layout)
|
|
451
|
+
tab.layout = { rows: [] };
|
|
452
|
+
if (!tab.layout.rows)
|
|
453
|
+
tab.layout.rows = [];
|
|
454
|
+
tab.layout.rows.push(newRow);
|
|
455
|
+
} else {
|
|
456
|
+
if (!doc.layout)
|
|
457
|
+
doc.layout = { rows: [] };
|
|
458
|
+
if (!doc.layout.rows)
|
|
459
|
+
doc.layout.rows = [];
|
|
460
|
+
doc.layout.rows.push(newRow);
|
|
461
|
+
}
|
|
462
|
+
await fs.writeFile(filePath, yaml.stringify(doc), "utf-8");
|
|
463
|
+
await ctx.configLoader.load();
|
|
464
|
+
return `Added chart "${chartName}" to dashboard "${dashboardName}"${tabName ? ` (tab: ${tabName})` : ""}`;
|
|
465
|
+
}
|
|
466
|
+
case "create_dashboard": {
|
|
467
|
+
const name = args.name;
|
|
468
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
469
|
+
return `Error: Invalid dashboard name "${name}". Use kebab-case (e.g. "sales-overview").`;
|
|
470
|
+
}
|
|
471
|
+
const title = args.title;
|
|
472
|
+
const description = args.description;
|
|
473
|
+
const chartNames = args.charts;
|
|
474
|
+
for (const chartName of chartNames) {
|
|
475
|
+
const chart = ctx.configLoader.getChartByName(chartName);
|
|
476
|
+
if (!chart)
|
|
477
|
+
return `Error: Chart "${chartName}" not found. Save the chart first using save_chart.`;
|
|
478
|
+
}
|
|
479
|
+
const rows = [];
|
|
480
|
+
let currentPairWidgets = [];
|
|
481
|
+
const flushPair = () => {
|
|
482
|
+
if (currentPairWidgets.length > 0) {
|
|
483
|
+
rows.push({ height: 300, widgets: currentPairWidgets });
|
|
484
|
+
currentPairWidgets = [];
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
for (const chartName of chartNames) {
|
|
488
|
+
const chart = ctx.configLoader.getChartByName(chartName);
|
|
489
|
+
const chartType = chart?.chart?.type ?? "";
|
|
490
|
+
if (chartType === "kpi") {
|
|
491
|
+
flushPair();
|
|
492
|
+
const lastRow = rows[rows.length - 1];
|
|
493
|
+
if (lastRow && lastRow.widgets.every((w) => w.cols === 3)) {
|
|
494
|
+
lastRow.widgets.push({ type: "chart", ref: chartName, cols: 3 });
|
|
495
|
+
} else {
|
|
496
|
+
rows.push({ height: 150, widgets: [{ type: "chart", ref: chartName, cols: 3 }] });
|
|
497
|
+
}
|
|
498
|
+
} else if (chartType === "line" || chartType === "area" || chartType === "combo" || chartType === "sankey") {
|
|
499
|
+
flushPair();
|
|
500
|
+
rows.push({ height: 300, widgets: [{ type: "chart", ref: chartName, cols: 12 }] });
|
|
501
|
+
} else {
|
|
502
|
+
currentPairWidgets.push({ type: "chart", ref: chartName, cols: 6 });
|
|
503
|
+
if (currentPairWidgets.length === 2) {
|
|
504
|
+
rows.push({ height: 300, widgets: currentPairWidgets });
|
|
505
|
+
currentPairWidgets = [];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
flushPair();
|
|
510
|
+
const dashObj = { name, title };
|
|
511
|
+
if (description)
|
|
512
|
+
dashObj.description = description;
|
|
513
|
+
dashObj.layout = { rows };
|
|
514
|
+
const fs = await import("fs/promises");
|
|
515
|
+
const path = await import("path");
|
|
516
|
+
const yamlLib = await import("yaml");
|
|
517
|
+
const dashDir = path.join(ctx.projectDir, "dashboards");
|
|
518
|
+
await fs.mkdir(dashDir, { recursive: true });
|
|
519
|
+
const filePath = path.join(dashDir, `${name}.yaml`);
|
|
520
|
+
await fs.writeFile(filePath, yamlLib.stringify(dashObj), "utf-8");
|
|
521
|
+
await ctx.configLoader.load();
|
|
522
|
+
ctx.emitEvent({ type: "navigate", data: { target: "dashboard", name, edit: true } });
|
|
523
|
+
return `Dashboard "${name}" created at dashboards/${name}.yaml with ${chartNames.length} chart(s).`;
|
|
524
|
+
}
|
|
525
|
+
case "call_agent": {
|
|
526
|
+
const agentName = args.agent;
|
|
527
|
+
const question = args.question;
|
|
528
|
+
if (!ctx.agentRegistry) {
|
|
529
|
+
return "Error: Agent registry not available.";
|
|
530
|
+
}
|
|
531
|
+
const agentConfig = ctx.agentRegistry.getAgent(agentName);
|
|
532
|
+
if (!agentConfig) {
|
|
533
|
+
const available = ctx.agentRegistry.listAgents().map((a) => a.name).join(", ");
|
|
534
|
+
return `Error: Agent "${agentName}" not found. Available agents: ${available}`;
|
|
535
|
+
}
|
|
536
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
537
|
+
if (!apiKey) {
|
|
538
|
+
return "Error: ANTHROPIC_API_KEY not set.";
|
|
539
|
+
}
|
|
540
|
+
const { StreamingChatAgent: Agent } = await import("./agent-KWKPAYT2.js");
|
|
541
|
+
const filteredConfig = {
|
|
542
|
+
...agentConfig,
|
|
543
|
+
tools: agentConfig.tools.filter((t) => t !== "call_agent")
|
|
544
|
+
};
|
|
545
|
+
const subAgent = new Agent(filteredConfig, apiKey);
|
|
546
|
+
const parentToolId = `call_agent_${agentName}_${Date.now()}`;
|
|
547
|
+
const subEmitEvent = (event) => {
|
|
548
|
+
if (event.type === "tool_start") {
|
|
549
|
+
ctx.emitEvent({ type: "tool_start", data: { ...event.data, parentToolId } });
|
|
550
|
+
} else if (event.type === "tool_end") {
|
|
551
|
+
ctx.emitEvent({ type: "tool_end", data: { ...event.data, parentToolId } });
|
|
552
|
+
} else if (event.type !== "done") {
|
|
553
|
+
ctx.emitEvent(event);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
const result = await Promise.race([
|
|
558
|
+
subAgent.run(agentConfig.systemPrompt, [{ role: "user", content: question }], ctx, subEmitEvent),
|
|
559
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Sub-agent timed out after 30s")), 3e4))
|
|
560
|
+
]);
|
|
561
|
+
return result.content || "Agent completed with no text response.";
|
|
562
|
+
} catch (err) {
|
|
563
|
+
return `Error from agent "${agentName}": ${err instanceof Error ? err.message : String(err)}`;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
default:
|
|
567
|
+
return `Unknown tool: ${toolName}`;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ../../packages/chat/dist/agent.js
|
|
572
|
+
var MAX_TOOL_ROUNDS = 10;
|
|
573
|
+
var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
|
|
574
|
+
var MAX_TOKENS = 4096;
|
|
575
|
+
var StreamingChatAgent = class {
|
|
576
|
+
client;
|
|
577
|
+
config;
|
|
578
|
+
toolDefs;
|
|
579
|
+
constructor(config, apiKey) {
|
|
580
|
+
this.client = new Anthropic({ apiKey });
|
|
581
|
+
this.config = config;
|
|
582
|
+
this.toolDefs = this.buildToolDefs();
|
|
583
|
+
}
|
|
584
|
+
buildToolDefs() {
|
|
585
|
+
if (this.config.tools.length === 0)
|
|
586
|
+
return [];
|
|
587
|
+
return CHAT_TOOL_DEFINITIONS.filter((t) => this.config.tools.includes(t.name));
|
|
588
|
+
}
|
|
589
|
+
getToolDefinitions() {
|
|
590
|
+
return this.toolDefs;
|
|
591
|
+
}
|
|
592
|
+
async run(systemPrompt, messages, toolContext, emitEvent) {
|
|
593
|
+
const model = this.config.model ?? DEFAULT_MODEL;
|
|
594
|
+
const allToolCalls = [];
|
|
595
|
+
let finalContent = "";
|
|
596
|
+
const anthropicTools = this.toolDefs.map((t) => ({
|
|
597
|
+
name: t.name,
|
|
598
|
+
description: t.description,
|
|
599
|
+
input_schema: t.parameters
|
|
600
|
+
}));
|
|
601
|
+
let currentMessages = [...messages];
|
|
602
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
603
|
+
const stream = this.client.messages.stream({
|
|
604
|
+
model,
|
|
605
|
+
max_tokens: MAX_TOKENS,
|
|
606
|
+
system: systemPrompt,
|
|
607
|
+
messages: currentMessages,
|
|
608
|
+
tools: anthropicTools.length > 0 ? anthropicTools : void 0
|
|
609
|
+
});
|
|
610
|
+
let roundText = "";
|
|
611
|
+
const roundToolUses = [];
|
|
612
|
+
for await (const event of stream) {
|
|
613
|
+
if (event.type === "content_block_delta") {
|
|
614
|
+
const delta = event.delta;
|
|
615
|
+
if (delta.type === "text_delta" && delta.text) {
|
|
616
|
+
roundText += delta.text;
|
|
617
|
+
emitEvent({ type: "text_delta", data: { text: delta.text } });
|
|
618
|
+
}
|
|
619
|
+
} else if (event.type === "content_block_start") {
|
|
620
|
+
const block = event.content_block;
|
|
621
|
+
if (block.type === "tool_use" && block.id && block.name) {
|
|
622
|
+
roundToolUses.push({ id: block.id, name: block.name, input: {} });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const finalMessage = await stream.finalMessage();
|
|
627
|
+
for (const block of finalMessage.content) {
|
|
628
|
+
if (block.type === "tool_use") {
|
|
629
|
+
const existing = roundToolUses.find((t) => t.id === block.id);
|
|
630
|
+
if (existing) {
|
|
631
|
+
existing.input = block.input;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
finalContent += roundText;
|
|
636
|
+
if (roundToolUses.length === 0 || finalMessage.stop_reason !== "tool_use") {
|
|
637
|
+
emitEvent({
|
|
638
|
+
type: "done",
|
|
639
|
+
data: { usage: { inputTokens: finalMessage.usage.input_tokens, outputTokens: finalMessage.usage.output_tokens } }
|
|
640
|
+
});
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
const toolResults = [];
|
|
644
|
+
for (const toolUse of roundToolUses) {
|
|
645
|
+
const toolCall = { id: toolUse.id, name: toolUse.name, args: toolUse.input, status: "running" };
|
|
646
|
+
emitEvent({ type: "tool_start", data: { id: toolUse.id, name: toolUse.name, args: toolUse.input } });
|
|
647
|
+
const startTime = Date.now();
|
|
648
|
+
try {
|
|
649
|
+
const result = await executeChatTool(toolUse.name, toolUse.input, toolContext);
|
|
650
|
+
toolCall.status = "completed";
|
|
651
|
+
toolCall.result = result;
|
|
652
|
+
toolCall.durationMs = Date.now() - startTime;
|
|
653
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result });
|
|
654
|
+
} catch (err) {
|
|
655
|
+
toolCall.status = "error";
|
|
656
|
+
toolCall.result = err instanceof Error ? err.message : String(err);
|
|
657
|
+
toolCall.durationMs = Date.now() - startTime;
|
|
658
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error: ${toolCall.result}` });
|
|
659
|
+
}
|
|
660
|
+
emitEvent({ type: "tool_end", data: { id: toolUse.id, name: toolUse.name, result: toolCall.result ?? "", durationMs: toolCall.durationMs ?? 0 } });
|
|
661
|
+
allToolCalls.push(toolCall);
|
|
662
|
+
}
|
|
663
|
+
currentMessages = [
|
|
664
|
+
...currentMessages,
|
|
665
|
+
{ role: "assistant", content: finalMessage.content },
|
|
666
|
+
{ role: "user", content: toolResults }
|
|
667
|
+
];
|
|
668
|
+
}
|
|
669
|
+
return { content: finalContent, toolCalls: allToolCalls };
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
export {
|
|
674
|
+
filterCatalogEntries,
|
|
675
|
+
StreamingChatAgent
|
|
676
|
+
};
|
|
677
|
+
//# sourceMappingURL=chunk-ZA6AOQVZ.js.map
|