yamchart 0.4.19 → 0.5.1
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-23GCWKME.js +373 -0
- package/dist/advisor-23GCWKME.js.map +1 -0
- package/dist/dist-ZRRM3OWF.js +651 -0
- package/dist/dist-ZRRM3OWF.js.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/public/assets/{LoginPage-BrI0Po1-.js → LoginPage-DMzsqzvn.js} +1 -1
- package/dist/public/assets/{PublicViewer-DNmvz77v.js → PublicViewer-0JqbgnmY.js} +1 -1
- package/dist/public/assets/{SetupWizard-DR7B3Vpm.js → SetupWizard-BGfmVQkR.js} +1 -1
- package/dist/public/assets/{ShareManagement-DyIWeXEC.js → ShareManagement-BExxd08S.js} +1 -1
- package/dist/public/assets/{UserManagement-D8OUccL0.js → UserManagement-DlxAXEeI.js} +1 -1
- package/dist/public/assets/index-Ds6UbsJz.js +165 -0
- package/dist/public/assets/{index-CKcEMrlp.css → index-v_QqvJAM.css} +1 -1
- package/dist/public/assets/{index.es-5wjag3AI.js → index.es-rFBtbxpx.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-fNEhp1P4.js → jspdf.es.min-CpbEoSkH.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/templates/default/CLAUDE.md +1 -0
- package/dist/templates/default/docs/yamchart-reference.md +15 -0
- package/package.json +4 -2
- package/dist/public/assets/index-uxK7mlHt.js +0 -165
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// ../../packages/advisor/dist/dbt/knowledge.js
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
import { join, dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
var KNOWLEDGE_DIR = join(__dirname, "..", "knowledge");
|
|
9
|
+
var KNOWLEDGE_TOPICS = [
|
|
10
|
+
"materializations",
|
|
11
|
+
"incremental",
|
|
12
|
+
"macros",
|
|
13
|
+
"testing",
|
|
14
|
+
"naming",
|
|
15
|
+
"performance"
|
|
16
|
+
];
|
|
17
|
+
async function loadKnowledge(topic) {
|
|
18
|
+
if (!KNOWLEDGE_TOPICS.includes(topic)) {
|
|
19
|
+
return `Unknown topic: "${topic}". Available topics: ${KNOWLEDGE_TOPICS.join(", ")}`;
|
|
20
|
+
}
|
|
21
|
+
const filePath = join(KNOWLEDGE_DIR, `${topic}.md`);
|
|
22
|
+
return readFile(filePath, "utf-8");
|
|
23
|
+
}
|
|
24
|
+
function getKnowledgeOverview() {
|
|
25
|
+
return `You have access to a dbt knowledge base via the get_knowledge tool. Available topics:
|
|
26
|
+
${KNOWLEDGE_TOPICS.map((t) => `- ${t}`).join("\n")}
|
|
27
|
+
|
|
28
|
+
Use get_knowledge when you need specifics on materializations, incremental strategies, Jinja macros, testing, naming conventions, or performance optimization.`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ../../packages/advisor/dist/dbt/project.js
|
|
32
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
33
|
+
import { join as join2, basename, relative, dirname as dirname2 } from "path";
|
|
34
|
+
import fg from "fast-glob";
|
|
35
|
+
import { parse as parseYaml } from "yaml";
|
|
36
|
+
async function getModelPaths(projectPath) {
|
|
37
|
+
const content = await readFile2(join2(projectPath, "dbt_project.yml"), "utf-8");
|
|
38
|
+
const parsed = parseYaml(content);
|
|
39
|
+
return parsed["model-paths"] ?? parsed.model_paths ?? ["models"];
|
|
40
|
+
}
|
|
41
|
+
function extractMaterialization(sql) {
|
|
42
|
+
const match = sql.match(/materialized\s*=\s*['"](\w+)['"]/);
|
|
43
|
+
return match?.[1] ?? "view";
|
|
44
|
+
}
|
|
45
|
+
function detectLayer(relativePath) {
|
|
46
|
+
const parts = relativePath.split("/");
|
|
47
|
+
return parts[0] ?? "unknown";
|
|
48
|
+
}
|
|
49
|
+
function detectPrefix(models) {
|
|
50
|
+
const prefixesByLayer = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const model of models) {
|
|
52
|
+
const underscoreIdx = model.name.indexOf("_");
|
|
53
|
+
if (underscoreIdx === -1)
|
|
54
|
+
continue;
|
|
55
|
+
const prefix = model.name.slice(0, underscoreIdx + 1);
|
|
56
|
+
if (!prefixesByLayer.has(model.layer)) {
|
|
57
|
+
prefixesByLayer.set(model.layer, /* @__PURE__ */ new Map());
|
|
58
|
+
}
|
|
59
|
+
const counts = prefixesByLayer.get(model.layer);
|
|
60
|
+
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
61
|
+
}
|
|
62
|
+
const result = {};
|
|
63
|
+
for (const [layer, counts] of prefixesByLayer) {
|
|
64
|
+
let maxCount = 0;
|
|
65
|
+
let maxPrefix = "";
|
|
66
|
+
for (const [prefix, count] of counts) {
|
|
67
|
+
if (count > maxCount) {
|
|
68
|
+
maxCount = count;
|
|
69
|
+
maxPrefix = prefix;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (maxPrefix && maxCount >= 2) {
|
|
73
|
+
result[layer] = maxPrefix;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function detectMaterializations(models) {
|
|
79
|
+
const matByLayer = /* @__PURE__ */ new Map();
|
|
80
|
+
for (const model of models) {
|
|
81
|
+
if (!matByLayer.has(model.layer)) {
|
|
82
|
+
matByLayer.set(model.layer, /* @__PURE__ */ new Map());
|
|
83
|
+
}
|
|
84
|
+
const counts = matByLayer.get(model.layer);
|
|
85
|
+
counts.set(model.materialization, (counts.get(model.materialization) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const [layer, counts] of matByLayer) {
|
|
89
|
+
let maxCount = 0;
|
|
90
|
+
let maxMat = "";
|
|
91
|
+
for (const [mat, count] of counts) {
|
|
92
|
+
if (count > maxCount) {
|
|
93
|
+
maxCount = count;
|
|
94
|
+
maxMat = mat;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (maxMat)
|
|
98
|
+
result[layer] = maxMat;
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
async function detectSchemaYmlPattern(projectPath, modelPaths) {
|
|
103
|
+
const ymlFiles = [];
|
|
104
|
+
for (const mp of modelPaths) {
|
|
105
|
+
const pattern = join2(projectPath, mp, "**/*.yml");
|
|
106
|
+
const files = await fg(pattern, { ignore: ["**/node_modules/**"] });
|
|
107
|
+
ymlFiles.push(...files);
|
|
108
|
+
}
|
|
109
|
+
if (ymlFiles.length === 0)
|
|
110
|
+
return "per-folder";
|
|
111
|
+
if (ymlFiles.length === 1)
|
|
112
|
+
return "single-file";
|
|
113
|
+
const dirs = new Set(ymlFiles.map((f) => dirname2(f)));
|
|
114
|
+
return dirs.size > 1 ? "per-folder" : "single-file";
|
|
115
|
+
}
|
|
116
|
+
async function detectConventions(projectPath) {
|
|
117
|
+
const modelPaths = await getModelPaths(projectPath);
|
|
118
|
+
const models = await listDbtModels(projectPath);
|
|
119
|
+
const folders = [...new Set(models.map((m) => m.layer))].filter((l) => l !== "unknown");
|
|
120
|
+
return {
|
|
121
|
+
folderStructure: folders,
|
|
122
|
+
namingPrefixes: detectPrefix(models),
|
|
123
|
+
commonMaterializations: detectMaterializations(models),
|
|
124
|
+
schemaYmlPattern: await detectSchemaYmlPattern(projectPath, modelPaths),
|
|
125
|
+
testPatterns: []
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function listDbtModels(projectPath) {
|
|
129
|
+
const modelPaths = await getModelPaths(projectPath);
|
|
130
|
+
const models = [];
|
|
131
|
+
for (const mp of modelPaths) {
|
|
132
|
+
const pattern = join2(projectPath, mp, "**/*.sql");
|
|
133
|
+
const files = await fg(pattern, { ignore: ["**/node_modules/**"] });
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const relPath = relative(join2(projectPath, mp), file);
|
|
136
|
+
const name = basename(file, ".sql");
|
|
137
|
+
const sql = await readFile2(file, "utf-8");
|
|
138
|
+
const materialization = extractMaterialization(sql);
|
|
139
|
+
const layer = detectLayer(relPath);
|
|
140
|
+
const folder = dirname2(relPath);
|
|
141
|
+
models.push({ name, path: relPath, layer, materialization, folder });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return models;
|
|
145
|
+
}
|
|
146
|
+
async function readDbtModel(projectPath, modelName) {
|
|
147
|
+
const models = await listDbtModels(projectPath);
|
|
148
|
+
const model = models.find((m) => m.name === modelName);
|
|
149
|
+
if (!model)
|
|
150
|
+
return null;
|
|
151
|
+
const modelPaths = await getModelPaths(projectPath);
|
|
152
|
+
for (const mp of modelPaths) {
|
|
153
|
+
const fullPath = join2(projectPath, mp, model.path);
|
|
154
|
+
try {
|
|
155
|
+
return await readFile2(fullPath, "utf-8");
|
|
156
|
+
} catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ../../packages/advisor/dist/tools.js
|
|
164
|
+
var TOOL_DEFINITIONS = [
|
|
165
|
+
// Read-only tools
|
|
166
|
+
{
|
|
167
|
+
name: "list_yamchart_models",
|
|
168
|
+
description: "List all yamchart SQL models with names, descriptions, parameters, and return columns",
|
|
169
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "list_charts",
|
|
173
|
+
description: "List all yamchart charts showing which models they reference and their chart type",
|
|
174
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "get_catalog",
|
|
178
|
+
description: "Get the dbt catalog (.yamchart/catalog.md) with upstream table schemas and column metadata",
|
|
179
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "introspect_warehouse",
|
|
183
|
+
description: "Query the live warehouse. Use for discovering raw tables not yet modeled in dbt.",
|
|
184
|
+
parameters: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
sql: { type: "string", description: "SQL query to execute (e.g. SHOW TABLES, SELECT * FROM information_schema.columns)" }
|
|
188
|
+
},
|
|
189
|
+
required: ["sql"]
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "sample_data",
|
|
194
|
+
description: "Preview rows from a table in the warehouse",
|
|
195
|
+
parameters: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
table: { type: "string", description: "Table name (can be schema-qualified)" },
|
|
199
|
+
limit: { type: "number", description: "Number of rows to return (default: 5)" }
|
|
200
|
+
},
|
|
201
|
+
required: ["table"]
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "read_dbt_model",
|
|
206
|
+
description: "Read the full SQL source of an existing dbt model",
|
|
207
|
+
parameters: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {
|
|
210
|
+
model_name: { type: "string", description: "Name of the dbt model to read" }
|
|
211
|
+
},
|
|
212
|
+
required: ["model_name"]
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "list_dbt_models",
|
|
217
|
+
description: "List all dbt models with their layer (staging/intermediate/marts), materialization, and folder",
|
|
218
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "get_knowledge",
|
|
222
|
+
description: "Look up dbt reference documentation on a topic: materializations, incremental, macros, testing, naming, performance",
|
|
223
|
+
parameters: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
topic: { type: "string", description: "Topic name: materializations, incremental, macros, testing, naming, performance" }
|
|
227
|
+
},
|
|
228
|
+
required: ["topic"]
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
// Action tools
|
|
232
|
+
{
|
|
233
|
+
name: "propose_model",
|
|
234
|
+
description: "Propose a new dbt model. Call this when you have a suggestion to present to the user.",
|
|
235
|
+
parameters: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
name: { type: "string", description: "Model name (snake_case)" },
|
|
239
|
+
description: { type: "string", description: "One-line description of what this model does" },
|
|
240
|
+
sql: { type: "string", description: "Full SQL for the model (may include Jinja)" },
|
|
241
|
+
layer: { type: "string", description: "dbt layer: staging, intermediate, or marts" },
|
|
242
|
+
materialization: { type: "string", description: "Materialization: view, table, or incremental" },
|
|
243
|
+
explanation: { type: "string", description: "Why this model is useful (2-3 sentences)" },
|
|
244
|
+
subfolder: { type: "string", description: 'Optional subfolder within the layer (e.g. "stripe", "core")' },
|
|
245
|
+
columns: {
|
|
246
|
+
type: "array",
|
|
247
|
+
description: "Column definitions for schema.yml",
|
|
248
|
+
items: {
|
|
249
|
+
type: "object",
|
|
250
|
+
properties: {
|
|
251
|
+
name: { type: "string" },
|
|
252
|
+
description: { type: "string" },
|
|
253
|
+
tests: { type: "array", items: { type: "string" } }
|
|
254
|
+
},
|
|
255
|
+
required: ["name"]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
required: ["name", "description", "sql", "layer", "materialization", "explanation"]
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "write_dbt_model",
|
|
264
|
+
description: "Write a proposed dbt model SQL file to the dbt project. Only call after proposing and user confirmation.",
|
|
265
|
+
parameters: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
name: { type: "string", description: "Model name (must match a previous proposal)" }
|
|
269
|
+
},
|
|
270
|
+
required: ["name"]
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "update_schema_yml",
|
|
275
|
+
description: "Add or update the model entry in the appropriate schema.yml file. Only call after writing the model.",
|
|
276
|
+
parameters: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
name: { type: "string", description: "Model name (must match a written model)" }
|
|
280
|
+
},
|
|
281
|
+
required: ["name"]
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
];
|
|
285
|
+
async function executeToolCall(context, toolName, input) {
|
|
286
|
+
switch (toolName) {
|
|
287
|
+
case "list_yamchart_models":
|
|
288
|
+
return JSON.stringify(context.yamchart.models.map((m) => ({
|
|
289
|
+
name: m.name,
|
|
290
|
+
description: m.description,
|
|
291
|
+
params: m.params,
|
|
292
|
+
returns: m.returns
|
|
293
|
+
})));
|
|
294
|
+
case "list_charts":
|
|
295
|
+
return JSON.stringify(context.yamchart.charts.map((c) => ({
|
|
296
|
+
name: c.name,
|
|
297
|
+
title: c.title,
|
|
298
|
+
model: c.model,
|
|
299
|
+
type: c.type
|
|
300
|
+
})));
|
|
301
|
+
case "get_catalog":
|
|
302
|
+
return context.yamchart.catalog ?? "No dbt catalog found. Run `yamchart sync-dbt` to create one.";
|
|
303
|
+
case "introspect_warehouse": {
|
|
304
|
+
if (!context.warehouse) {
|
|
305
|
+
return "Warehouse introspection not available. No database connection configured.";
|
|
306
|
+
}
|
|
307
|
+
const sql = input.sql;
|
|
308
|
+
try {
|
|
309
|
+
const result = await context.warehouse.executeSql(sql);
|
|
310
|
+
return JSON.stringify(result);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
case "sample_data": {
|
|
316
|
+
if (!context.warehouse) {
|
|
317
|
+
return "Warehouse not available. No database connection configured.";
|
|
318
|
+
}
|
|
319
|
+
const table = input.table;
|
|
320
|
+
const limit = input.limit ?? 5;
|
|
321
|
+
try {
|
|
322
|
+
const result = await context.warehouse.executeSql(`SELECT * FROM ${table} LIMIT ${limit}`);
|
|
323
|
+
return JSON.stringify(result);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
case "read_dbt_model": {
|
|
329
|
+
const modelName = input.model_name;
|
|
330
|
+
const sql = await readDbtModel(context.dbt.projectPath, modelName);
|
|
331
|
+
if (!sql) {
|
|
332
|
+
return JSON.stringify({ error: `Model not found: ${modelName}` });
|
|
333
|
+
}
|
|
334
|
+
return JSON.stringify({ name: modelName, sql });
|
|
335
|
+
}
|
|
336
|
+
case "list_dbt_models":
|
|
337
|
+
return JSON.stringify(context.dbt.models.map((m) => ({
|
|
338
|
+
name: m.name,
|
|
339
|
+
path: m.path,
|
|
340
|
+
layer: m.layer,
|
|
341
|
+
materialization: m.materialization,
|
|
342
|
+
folder: m.folder
|
|
343
|
+
})));
|
|
344
|
+
case "get_knowledge": {
|
|
345
|
+
const topic = input.topic;
|
|
346
|
+
return loadKnowledge(topic);
|
|
347
|
+
}
|
|
348
|
+
case "propose_model":
|
|
349
|
+
return JSON.stringify({
|
|
350
|
+
status: "proposed",
|
|
351
|
+
name: input.name,
|
|
352
|
+
description: input.description,
|
|
353
|
+
sql: input.sql,
|
|
354
|
+
layer: input.layer,
|
|
355
|
+
materialization: input.materialization,
|
|
356
|
+
explanation: input.explanation,
|
|
357
|
+
subfolder: input.subfolder,
|
|
358
|
+
columns: input.columns
|
|
359
|
+
});
|
|
360
|
+
case "write_dbt_model":
|
|
361
|
+
return JSON.stringify({
|
|
362
|
+
status: "pending_confirmation",
|
|
363
|
+
name: input.name,
|
|
364
|
+
message: "Model write requested. Waiting for user confirmation."
|
|
365
|
+
});
|
|
366
|
+
case "update_schema_yml":
|
|
367
|
+
return JSON.stringify({
|
|
368
|
+
status: "pending_confirmation",
|
|
369
|
+
name: input.name,
|
|
370
|
+
message: "Schema.yml update requested. Waiting for user confirmation."
|
|
371
|
+
});
|
|
372
|
+
default:
|
|
373
|
+
return JSON.stringify({ error: `Unknown tool: ${toolName}` });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ../../packages/advisor/dist/agent.js
|
|
378
|
+
var SYSTEM_PROMPT = `You are a dbt advisor for yamchart projects. You analyze the BI layer (yamchart models, charts, dashboards) and the data engineering layer (dbt models, warehouse tables) to suggest improvements to dbt models.
|
|
379
|
+
|
|
380
|
+
Think like a senior data engineer. Your goals:
|
|
381
|
+
- Understand what the BI layer needs (charts, filters, drill-downs, date ranges)
|
|
382
|
+
- Identify gaps in the dbt project (missing models, incomplete staging, opportunities for pre-aggregation)
|
|
383
|
+
- Suggest new dbt models that serve the BI layer better
|
|
384
|
+
- Follow the project's existing conventions (folder structure, naming, materializations)
|
|
385
|
+
|
|
386
|
+
When proposing models:
|
|
387
|
+
- Match the project's naming conventions (detect and follow existing prefixes like stg_, fct_, dim_)
|
|
388
|
+
- Place models in the correct layer and folder
|
|
389
|
+
- Choose appropriate materializations
|
|
390
|
+
- Include column descriptions and tests in proposals
|
|
391
|
+
- Explain WHY the model helps, not just WHAT it does
|
|
392
|
+
- Use ref() and source() macros correctly
|
|
393
|
+
|
|
394
|
+
When in audit mode:
|
|
395
|
+
- Evaluate: coverage gaps, convention violations, materialization mismatches, missing tests
|
|
396
|
+
- Rank suggestions by impact
|
|
397
|
+
- Be concise \u2014 focus on actionable improvements
|
|
398
|
+
|
|
399
|
+
${getKnowledgeOverview()}
|
|
400
|
+
|
|
401
|
+
When you have a suggestion, call propose_model with the full SQL and metadata. Do NOT call write_dbt_model or update_schema_yml directly \u2014 the user will be prompted to confirm before writing.`;
|
|
402
|
+
var MAX_TOOL_ROUNDS = 15;
|
|
403
|
+
var AdvisorAgent = class {
|
|
404
|
+
provider;
|
|
405
|
+
constructor(provider) {
|
|
406
|
+
this.provider = provider;
|
|
407
|
+
}
|
|
408
|
+
async run(context, userMessages) {
|
|
409
|
+
const proposals = [];
|
|
410
|
+
const messages = [...userMessages];
|
|
411
|
+
const systemPrompt = this.buildSystemPrompt(context);
|
|
412
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
413
|
+
const response = await this.provider.chat({
|
|
414
|
+
system: systemPrompt,
|
|
415
|
+
messages,
|
|
416
|
+
tools: TOOL_DEFINITIONS
|
|
417
|
+
});
|
|
418
|
+
const textBlocks = response.content.filter((b) => b.type === "text");
|
|
419
|
+
const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
|
|
420
|
+
if (toolUseBlocks.length === 0) {
|
|
421
|
+
const responseText = textBlocks.map((b) => b.text).join("\n");
|
|
422
|
+
return { response: responseText, proposals, messages };
|
|
423
|
+
}
|
|
424
|
+
messages.push({ role: "assistant", content: response.content });
|
|
425
|
+
const toolResults = [];
|
|
426
|
+
for (const toolUse of toolUseBlocks) {
|
|
427
|
+
const result = await executeToolCall(context, toolUse.name, toolUse.input);
|
|
428
|
+
if (toolUse.name === "propose_model") {
|
|
429
|
+
const parsed = JSON.parse(result);
|
|
430
|
+
proposals.push({
|
|
431
|
+
name: parsed.name,
|
|
432
|
+
description: parsed.description,
|
|
433
|
+
sql: toolUse.input.sql,
|
|
434
|
+
layer: toolUse.input.layer,
|
|
435
|
+
materialization: toolUse.input.materialization,
|
|
436
|
+
explanation: toolUse.input.explanation,
|
|
437
|
+
subfolder: toolUse.input.subfolder,
|
|
438
|
+
columns: toolUse.input.columns
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
toolResults.push({
|
|
442
|
+
type: "tool_result",
|
|
443
|
+
tool_use_id: toolUse.id,
|
|
444
|
+
content: result
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
messages.push({ role: "user", content: toolResults });
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
response: "Reached maximum tool call rounds. Here are the suggestions gathered so far.",
|
|
451
|
+
proposals,
|
|
452
|
+
messages
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
buildSystemPrompt(context) {
|
|
456
|
+
const parts = [SYSTEM_PROMPT];
|
|
457
|
+
parts.push(`
|
|
458
|
+
## Current Project Summary`);
|
|
459
|
+
parts.push(`- Yamchart: ${context.yamchart.models.length} SQL models, ${context.yamchart.charts.length} charts, ${context.yamchart.dashboards.length} dashboards`);
|
|
460
|
+
parts.push(`- dbt project: "${context.dbt.projectName}" at ${context.dbt.projectPath}`);
|
|
461
|
+
parts.push(`- dbt models: ${context.dbt.models.length} (layers: ${context.dbt.conventions.folderStructure.join(", ") || "none detected"})`);
|
|
462
|
+
if (Object.keys(context.dbt.conventions.namingPrefixes).length > 0) {
|
|
463
|
+
const prefixes = Object.entries(context.dbt.conventions.namingPrefixes).map(([layer, prefix]) => `${layer}: ${prefix}`).join(", ");
|
|
464
|
+
parts.push(`- Naming prefixes: ${prefixes}`);
|
|
465
|
+
}
|
|
466
|
+
if (Object.keys(context.dbt.conventions.commonMaterializations).length > 0) {
|
|
467
|
+
const mats = Object.entries(context.dbt.conventions.commonMaterializations).map(([layer, mat]) => `${layer}: ${mat}`).join(", ");
|
|
468
|
+
parts.push(`- Materializations: ${mats}`);
|
|
469
|
+
}
|
|
470
|
+
parts.push(`- Schema.yml pattern: ${context.dbt.conventions.schemaYmlPattern}`);
|
|
471
|
+
parts.push(`- Catalog: ${context.yamchart.catalog ? "available" : "not synced"}`);
|
|
472
|
+
parts.push(`- Warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : "not connected"}`);
|
|
473
|
+
return parts.join("\n");
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// ../../packages/advisor/dist/providers/anthropic.js
|
|
478
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
479
|
+
var AnthropicProvider = class {
|
|
480
|
+
client;
|
|
481
|
+
model;
|
|
482
|
+
constructor(apiKey, model = "claude-sonnet-4-5-20250929") {
|
|
483
|
+
this.client = new Anthropic({ apiKey });
|
|
484
|
+
this.model = model;
|
|
485
|
+
}
|
|
486
|
+
async chat(options) {
|
|
487
|
+
const tools = options.tools.map((t) => ({
|
|
488
|
+
name: t.name,
|
|
489
|
+
description: t.description,
|
|
490
|
+
input_schema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: t.parameters.properties,
|
|
493
|
+
required: t.parameters.required ?? []
|
|
494
|
+
}
|
|
495
|
+
}));
|
|
496
|
+
const messages = options.messages.map((m) => {
|
|
497
|
+
if (typeof m.content === "string") {
|
|
498
|
+
return { role: m.role, content: m.content };
|
|
499
|
+
}
|
|
500
|
+
const blocks = m.content.map((block) => {
|
|
501
|
+
if (block.type === "text") {
|
|
502
|
+
return { type: "text", text: block.text };
|
|
503
|
+
}
|
|
504
|
+
if (block.type === "tool_use") {
|
|
505
|
+
const tu = block;
|
|
506
|
+
return { type: "tool_use", id: tu.id, name: tu.name, input: tu.input };
|
|
507
|
+
}
|
|
508
|
+
if (block.type === "tool_result") {
|
|
509
|
+
const tr = block;
|
|
510
|
+
return { type: "tool_result", tool_use_id: tr.tool_use_id, content: tr.content };
|
|
511
|
+
}
|
|
512
|
+
throw new Error(`Unknown block type: ${block.type}`);
|
|
513
|
+
});
|
|
514
|
+
return { role: m.role, content: blocks };
|
|
515
|
+
});
|
|
516
|
+
const response = await this.client.messages.create({
|
|
517
|
+
model: this.model,
|
|
518
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
519
|
+
system: options.system,
|
|
520
|
+
tools,
|
|
521
|
+
messages
|
|
522
|
+
});
|
|
523
|
+
const content = response.content.map((block) => {
|
|
524
|
+
if (block.type === "text") {
|
|
525
|
+
return { type: "text", text: block.text };
|
|
526
|
+
}
|
|
527
|
+
if (block.type === "tool_use") {
|
|
528
|
+
return {
|
|
529
|
+
type: "tool_use",
|
|
530
|
+
id: block.id,
|
|
531
|
+
name: block.name,
|
|
532
|
+
input: block.input
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
throw new Error(`Unexpected block type: ${block.type}`);
|
|
536
|
+
});
|
|
537
|
+
return {
|
|
538
|
+
content,
|
|
539
|
+
stopReason: response.stop_reason === "tool_use" ? "tool_use" : "end"
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// ../../packages/advisor/dist/dbt/writer.js
|
|
545
|
+
import { readFile as readFile3, writeFile, mkdir, access } from "fs/promises";
|
|
546
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
547
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
548
|
+
function buildModelPath(modelName, layer, conventions, subfolder) {
|
|
549
|
+
const effectiveLayer = layer ?? (conventions.folderStructure.includes("marts") ? "marts" : conventions.folderStructure[0] ?? "models");
|
|
550
|
+
const parts = ["models", effectiveLayer];
|
|
551
|
+
if (subfolder)
|
|
552
|
+
parts.push(subfolder);
|
|
553
|
+
parts.push(`${modelName}.sql`);
|
|
554
|
+
return parts.join("/");
|
|
555
|
+
}
|
|
556
|
+
function formatModelSql(options) {
|
|
557
|
+
const lines = [];
|
|
558
|
+
if (options.materialization !== "view") {
|
|
559
|
+
const configParts = [`materialized='${options.materialization}'`];
|
|
560
|
+
if (options.tags && options.tags.length > 0) {
|
|
561
|
+
configParts.push(`tags=[${options.tags.map((t) => `'${t}'`).join(", ")}]`);
|
|
562
|
+
}
|
|
563
|
+
lines.push(`{{
|
|
564
|
+
config(
|
|
565
|
+
${configParts.join(",\n ")}
|
|
566
|
+
)
|
|
567
|
+
}}
|
|
568
|
+
`);
|
|
569
|
+
}
|
|
570
|
+
lines.push(options.sql);
|
|
571
|
+
return lines.join("\n");
|
|
572
|
+
}
|
|
573
|
+
function buildSchemaYmlEntry(options) {
|
|
574
|
+
const model = {
|
|
575
|
+
name: options.name,
|
|
576
|
+
description: options.description
|
|
577
|
+
};
|
|
578
|
+
if (options.columns && options.columns.length > 0) {
|
|
579
|
+
model.columns = options.columns.map((col) => {
|
|
580
|
+
const entry = { name: col.name };
|
|
581
|
+
if (col.description)
|
|
582
|
+
entry.description = col.description;
|
|
583
|
+
if (col.tests && col.tests.length > 0)
|
|
584
|
+
entry.tests = col.tests;
|
|
585
|
+
return entry;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return stringifyYaml({ models: [model] }, { indent: 2 });
|
|
589
|
+
}
|
|
590
|
+
async function writeDbtModel(projectPath, modelPath, sql) {
|
|
591
|
+
const fullPath = join3(projectPath, modelPath);
|
|
592
|
+
await mkdir(dirname3(fullPath), { recursive: true });
|
|
593
|
+
await writeFile(fullPath, sql + "\n", "utf-8");
|
|
594
|
+
return fullPath;
|
|
595
|
+
}
|
|
596
|
+
async function updateSchemaYml(projectPath, schemaPath, entry) {
|
|
597
|
+
const fullPath = join3(projectPath, schemaPath);
|
|
598
|
+
let existing = {
|
|
599
|
+
version: 2,
|
|
600
|
+
models: []
|
|
601
|
+
};
|
|
602
|
+
try {
|
|
603
|
+
await access(fullPath);
|
|
604
|
+
const content = await readFile3(fullPath, "utf-8");
|
|
605
|
+
existing = parseYaml2(content);
|
|
606
|
+
if (!existing.models)
|
|
607
|
+
existing.models = [];
|
|
608
|
+
} catch {
|
|
609
|
+
await mkdir(dirname3(fullPath), { recursive: true });
|
|
610
|
+
}
|
|
611
|
+
const idx = existing.models.findIndex((m) => m.name === entry.name);
|
|
612
|
+
const modelEntry = {
|
|
613
|
+
name: entry.name,
|
|
614
|
+
description: entry.description
|
|
615
|
+
};
|
|
616
|
+
if (entry.columns && entry.columns.length > 0) {
|
|
617
|
+
modelEntry.columns = entry.columns.map((col) => {
|
|
618
|
+
const c = { name: col.name };
|
|
619
|
+
if (col.description)
|
|
620
|
+
c.description = col.description;
|
|
621
|
+
if (col.tests && col.tests.length > 0)
|
|
622
|
+
c.tests = col.tests;
|
|
623
|
+
return c;
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (idx >= 0) {
|
|
627
|
+
existing.models[idx] = modelEntry;
|
|
628
|
+
} else {
|
|
629
|
+
existing.models.push(modelEntry);
|
|
630
|
+
}
|
|
631
|
+
await writeFile(fullPath, stringifyYaml(existing, { indent: 2 }), "utf-8");
|
|
632
|
+
return fullPath;
|
|
633
|
+
}
|
|
634
|
+
export {
|
|
635
|
+
AdvisorAgent,
|
|
636
|
+
AnthropicProvider,
|
|
637
|
+
KNOWLEDGE_TOPICS,
|
|
638
|
+
SYSTEM_PROMPT,
|
|
639
|
+
TOOL_DEFINITIONS,
|
|
640
|
+
buildModelPath,
|
|
641
|
+
buildSchemaYmlEntry,
|
|
642
|
+
detectConventions,
|
|
643
|
+
formatModelSql,
|
|
644
|
+
getKnowledgeOverview,
|
|
645
|
+
listDbtModels,
|
|
646
|
+
loadKnowledge,
|
|
647
|
+
readDbtModel,
|
|
648
|
+
updateSchemaYml,
|
|
649
|
+
writeDbtModel
|
|
650
|
+
};
|
|
651
|
+
//# sourceMappingURL=dist-ZRRM3OWF.js.map
|