@databrainhq/mcp-server 0.3.0-rc.5 → 0.3.0-rc.7
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/README.md +23 -2
- package/dist/apps/server.js +3 -3
- package/dist/apps/stdio.js +4 -4
- package/dist/{chunk-4DYPD273.js → chunk-DYFJ5GKQ.js} +3 -3
- package/dist/{chunk-PRE4CGOV.js → chunk-ETXH5HTT.js} +56 -24
- package/dist/chunk-ETXH5HTT.js.map +1 -0
- package/dist/{chunk-5IQ7MVUH.js → chunk-JSYG7ULR.js} +653 -165
- package/dist/chunk-JSYG7ULR.js.map +1 -0
- package/dist/{chunk-V64ZQINV.js → chunk-VSSGMPSL.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/{main-Q6XT4O46.js → main-AZ26BAEM.js} +9 -3
- package/dist/{main-Q6XT4O46.js.map → main-AZ26BAEM.js.map} +1 -1
- package/package.json +2 -1
- package/dist/chunk-5IQ7MVUH.js.map +0 -1
- package/dist/chunk-PRE4CGOV.js.map +0 -1
- /package/dist/{chunk-4DYPD273.js.map → chunk-DYFJ5GKQ.js.map} +0 -0
- /package/dist/{chunk-V64ZQINV.js.map → chunk-VSSGMPSL.js.map} +0 -0
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
toolError,
|
|
19
19
|
toolSuccess,
|
|
20
20
|
validateBundle
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-ETXH5HTT.js";
|
|
22
22
|
|
|
23
23
|
// src/apps/contract.ts
|
|
24
24
|
import { z } from "zod";
|
|
@@ -178,6 +178,12 @@ var TOOL_METADATA = {
|
|
|
178
178
|
preview_chart: { category: "internal_analytics", workflowIntent: "query_data", effect: "view" },
|
|
179
179
|
save_chart_as_dashboard: { category: "internal_analytics", workflowIntent: "author_dashboard", effect: "commit" },
|
|
180
180
|
// Semantic layer
|
|
181
|
+
search_semantic_model: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
|
|
182
|
+
get_semantic_entity: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
|
|
183
|
+
propose_semantic_change: { category: "semantic_layer", workflowIntent: "model_data", effect: "read" },
|
|
184
|
+
apply_semantic_change: { category: "semantic_layer", workflowIntent: "model_data", effect: "commit" },
|
|
185
|
+
eval_semantic_model: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
|
|
186
|
+
semantic_readiness: { category: "semantic_layer", workflowIntent: "model_data", effect: "read" },
|
|
181
187
|
get_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
|
|
182
188
|
update_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
|
|
183
189
|
start_semantic_layer_generation: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
|
|
@@ -492,18 +498,18 @@ var QueryApi = class {
|
|
|
492
498
|
throw err;
|
|
493
499
|
}
|
|
494
500
|
}
|
|
495
|
-
async startSemanticLayerGeneration(
|
|
501
|
+
async startSemanticLayerGeneration(datamartId2) {
|
|
496
502
|
return this.client.request({
|
|
497
503
|
method: "POST",
|
|
498
|
-
path: `/api/v2/data-app/datamarts/${encodeURIComponent(
|
|
504
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generate`,
|
|
499
505
|
auth: "service",
|
|
500
506
|
invalidateCache: ["semantic_layer_"]
|
|
501
507
|
});
|
|
502
508
|
}
|
|
503
|
-
async getSemanticLayerGenerationStatus(
|
|
509
|
+
async getSemanticLayerGenerationStatus(datamartId2) {
|
|
504
510
|
return this.client.request({
|
|
505
511
|
method: "GET",
|
|
506
|
-
path: `/api/v2/data-app/datamarts/${encodeURIComponent(
|
|
512
|
+
path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
|
|
507
513
|
auth: "service"
|
|
508
514
|
});
|
|
509
515
|
}
|
|
@@ -526,9 +532,9 @@ function normalizeRecords(value) {
|
|
|
526
532
|
record.items,
|
|
527
533
|
record.data
|
|
528
534
|
];
|
|
529
|
-
for (const
|
|
530
|
-
if (Array.isArray(
|
|
531
|
-
return
|
|
535
|
+
for (const candidate2 of candidates) {
|
|
536
|
+
if (Array.isArray(candidate2)) {
|
|
537
|
+
return candidate2.filter((item) => asRecord(item) !== null);
|
|
532
538
|
}
|
|
533
539
|
}
|
|
534
540
|
return [];
|
|
@@ -557,6 +563,65 @@ var ScheduledReportsApi = class {
|
|
|
557
563
|
}
|
|
558
564
|
};
|
|
559
565
|
|
|
566
|
+
// src/api/semantic-model-api.ts
|
|
567
|
+
var SemanticModelApi = class {
|
|
568
|
+
constructor(client) {
|
|
569
|
+
this.client = client;
|
|
570
|
+
}
|
|
571
|
+
path(id2, verb) {
|
|
572
|
+
return `/api/v2/semantic/${encodeURIComponent(id2)}/${verb}`;
|
|
573
|
+
}
|
|
574
|
+
pull(id2) {
|
|
575
|
+
return this.client.request({ method: "GET", path: this.path(id2, "pull"), auth: "service" });
|
|
576
|
+
}
|
|
577
|
+
retrieve(id2, q, k) {
|
|
578
|
+
return this.client.request({
|
|
579
|
+
method: "GET",
|
|
580
|
+
path: this.path(id2, "retrieve"),
|
|
581
|
+
auth: "service",
|
|
582
|
+
query: { q, k: String(k) }
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
validate(id2, source2) {
|
|
586
|
+
return this.client.request({
|
|
587
|
+
method: "POST",
|
|
588
|
+
path: this.path(id2, "validate"),
|
|
589
|
+
auth: "service",
|
|
590
|
+
body: { source: source2 }
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
plan(id2, source2, expected_content_hash) {
|
|
594
|
+
return this.client.request({
|
|
595
|
+
method: "POST",
|
|
596
|
+
path: this.path(id2, "plan"),
|
|
597
|
+
auth: "service",
|
|
598
|
+
body: { source: source2, expected_content_hash }
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
apply(id2, source2, expected_content_hash) {
|
|
602
|
+
return this.client.request({
|
|
603
|
+
method: "POST",
|
|
604
|
+
path: this.path(id2, "apply"),
|
|
605
|
+
auth: "service",
|
|
606
|
+
body: { source: source2, expected_content_hash },
|
|
607
|
+
noRetryOnTimeout: true
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
evaluate(id2, body) {
|
|
611
|
+
return this.client.request({
|
|
612
|
+
method: "POST",
|
|
613
|
+
path: this.path(id2, "eval"),
|
|
614
|
+
auth: "service",
|
|
615
|
+
body,
|
|
616
|
+
timeoutMs: 3e5,
|
|
617
|
+
noRetryOnTimeout: true
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
readiness(id2) {
|
|
621
|
+
return this.client.request({ method: "GET", path: this.path(id2, "readiness"), auth: "service" });
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
560
625
|
// src/api/tenants-api.ts
|
|
561
626
|
var TenantsApi = class {
|
|
562
627
|
constructor(client) {
|
|
@@ -739,8 +804,8 @@ function extractAccessSettings(embed) {
|
|
|
739
804
|
asRecord2(metadataConfig?.accessSettings),
|
|
740
805
|
asRecord2(options?.accessSettings)
|
|
741
806
|
].filter((c) => !!c);
|
|
742
|
-
for (const
|
|
743
|
-
if (Object.keys(
|
|
807
|
+
for (const candidate2 of candidates) {
|
|
808
|
+
if (Object.keys(candidate2).length > 0) return candidate2;
|
|
744
809
|
}
|
|
745
810
|
return null;
|
|
746
811
|
}
|
|
@@ -1472,6 +1537,7 @@ function createServiceBundle(client) {
|
|
|
1472
1537
|
const guestTokenApi = new GuestTokenApi(client);
|
|
1473
1538
|
const queryApi = new QueryApi(client);
|
|
1474
1539
|
const scheduledReportsApi = new ScheduledReportsApi(client);
|
|
1540
|
+
const semanticModelApi = new SemanticModelApi(client);
|
|
1475
1541
|
const customChartsApi = new CustomChartsApi(client);
|
|
1476
1542
|
const tenantsApi = new TenantsApi(client);
|
|
1477
1543
|
const widgetsApi = new WidgetsApi(client);
|
|
@@ -1487,6 +1553,7 @@ function createServiceBundle(client) {
|
|
|
1487
1553
|
workspacesApi,
|
|
1488
1554
|
queryApi,
|
|
1489
1555
|
scheduledReportsApi,
|
|
1556
|
+
semanticModelApi,
|
|
1490
1557
|
customChartsApi,
|
|
1491
1558
|
tenantsApi,
|
|
1492
1559
|
widgetsApi,
|
|
@@ -5316,16 +5383,403 @@ ${result.feedback}`);
|
|
|
5316
5383
|
);
|
|
5317
5384
|
}
|
|
5318
5385
|
|
|
5319
|
-
// src/tools/query/
|
|
5386
|
+
// src/tools/query/semantic-model.ts
|
|
5320
5387
|
import { z as z27 } from "zod";
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5388
|
+
|
|
5389
|
+
// src/services/semantic-source.ts
|
|
5390
|
+
import { parseDocument, stringify as stringify2 } from "yaml";
|
|
5391
|
+
var ENTITY_COLLECTIONS = {
|
|
5392
|
+
table: ["tables"],
|
|
5393
|
+
column: ["columns"],
|
|
5394
|
+
measure: ["measures"],
|
|
5395
|
+
metric: ["metrics"],
|
|
5396
|
+
join: ["joins"],
|
|
5397
|
+
entity: ["entities"],
|
|
5398
|
+
glossary: ["glossary"],
|
|
5399
|
+
saved_query: ["saved_queries", "verified_queries"],
|
|
5400
|
+
instruction: ["instructions", "agent_instructions"],
|
|
5401
|
+
access_rule: ["access", "policies"]
|
|
5402
|
+
};
|
|
5403
|
+
function isObject(value) {
|
|
5404
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5405
|
+
}
|
|
5406
|
+
function assertSafe(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
5407
|
+
if (depth > 100) throw new Error("Semantic source nesting exceeds 100 levels.");
|
|
5408
|
+
if (!value || typeof value !== "object") return;
|
|
5409
|
+
if (seen.has(value)) throw new Error("Recursive YAML aliases are not supported.");
|
|
5410
|
+
seen.add(value);
|
|
5411
|
+
for (const [key, child] of Object.entries(value)) {
|
|
5412
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) throw new Error(`Unsupported source key: ${key}.`);
|
|
5413
|
+
assertSafe(child, seen, depth + 1);
|
|
5414
|
+
}
|
|
5415
|
+
seen.delete(value);
|
|
5416
|
+
}
|
|
5417
|
+
function parseSemanticSource(source2) {
|
|
5418
|
+
const document = parseDocument(source2, { prettyErrors: false, uniqueKeys: true });
|
|
5419
|
+
if (document.errors.length || document.warnings.length) {
|
|
5420
|
+
throw new Error((document.errors[0] ?? document.warnings[0]).message);
|
|
5421
|
+
}
|
|
5422
|
+
const value = document.toJS({ maxAliasCount: 100 });
|
|
5423
|
+
if (!isObject(value)) throw new Error("Semantic source must be a YAML mapping.");
|
|
5424
|
+
assertSafe(value);
|
|
5425
|
+
return value;
|
|
5426
|
+
}
|
|
5427
|
+
function semanticEntities(source2, visibleOnly = false) {
|
|
5428
|
+
const out = [];
|
|
5429
|
+
for (const [kind, keys] of Object.entries(ENTITY_COLLECTIONS)) {
|
|
5430
|
+
if (kind === "column") continue;
|
|
5431
|
+
const key = keys.find((key2) => Array.isArray(source2[key2]));
|
|
5432
|
+
const entries = key ? source2[key] : [];
|
|
5433
|
+
for (const entry of entries) {
|
|
5434
|
+
if (!isObject(entry) || typeof entry.name !== "string" || visibleOnly && entry.hidden === true) continue;
|
|
5435
|
+
const columns = Array.isArray(entry.columns) ? entry.columns.filter(isObject) : [];
|
|
5436
|
+
const entity = visibleOnly && kind === "table" ? { ...entry, columns: columns.filter((column) => column.hidden !== true) } : entry;
|
|
5437
|
+
out.push({ id: `${kind}:${entry.name}`, kind, name: entry.name, entity });
|
|
5438
|
+
if (kind === "table") for (const column of columns) {
|
|
5439
|
+
if (typeof column.name !== "string" || visibleOnly && column.hidden === true) continue;
|
|
5440
|
+
const name = `${entry.name}.${column.name}`;
|
|
5441
|
+
out.push({ id: `column:${name}`, kind: "column", name, entity: column });
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
return out;
|
|
5446
|
+
}
|
|
5447
|
+
function mergePatch(base, patch) {
|
|
5448
|
+
if (isObject(patch)) {
|
|
5449
|
+
const next = isObject(base) ? { ...base } : {};
|
|
5450
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
5451
|
+
if (value === null) delete next[key];
|
|
5452
|
+
else next[key] = mergePatch(next[key], value);
|
|
5453
|
+
}
|
|
5454
|
+
return next;
|
|
5455
|
+
}
|
|
5456
|
+
if (Array.isArray(patch) && patch.length && patch.every((item) => isObject(item) && typeof item.name === "string")) {
|
|
5457
|
+
const next = Array.isArray(base) ? [...base] : [];
|
|
5458
|
+
const names = /* @__PURE__ */ new Set();
|
|
5459
|
+
for (const item of patch) {
|
|
5460
|
+
const name = item.name;
|
|
5461
|
+
if (names.has(name)) throw new Error(`Duplicate patch entity: ${name}.`);
|
|
5462
|
+
names.add(name);
|
|
5463
|
+
const index = next.findIndex((entry) => isObject(entry) && entry.name === name);
|
|
5464
|
+
if (index < 0) next.push(mergePatch({}, item));
|
|
5465
|
+
else next[index] = mergePatch(next[index], item);
|
|
5466
|
+
}
|
|
5467
|
+
return next;
|
|
5468
|
+
}
|
|
5469
|
+
return patch;
|
|
5470
|
+
}
|
|
5471
|
+
function changeSemanticSource(source2, input) {
|
|
5472
|
+
const model = parseSemanticSource(source2);
|
|
5473
|
+
if (input.yaml_patch !== void 0) return stringify2(mergePatch(model, parseSemanticSource(input.yaml_patch)));
|
|
5474
|
+
const change = input.change;
|
|
5475
|
+
assertSafe(change);
|
|
5476
|
+
let parent = model;
|
|
5477
|
+
if (change.kind === "column") {
|
|
5478
|
+
if (!change.table) throw new Error("A column change requires table and an unqualified column name.");
|
|
5479
|
+
const table = (Array.isArray(model.tables) ? model.tables : []).find(
|
|
5480
|
+
(item) => isObject(item) && item.name === change.table
|
|
5481
|
+
);
|
|
5482
|
+
if (!isObject(table)) throw new Error(`Table not found: ${change.table}.`);
|
|
5483
|
+
parent = table;
|
|
5484
|
+
} else if (change.table) throw new Error("table is only valid for a column change.");
|
|
5485
|
+
const keys = ENTITY_COLLECTIONS[change.kind];
|
|
5486
|
+
const key = keys.find((key2) => Array.isArray(parent[key2])) ?? keys[0];
|
|
5487
|
+
const entries = Array.isArray(parent[key]) ? [...parent[key]] : [];
|
|
5488
|
+
const index = entries.findIndex((entry) => isObject(entry) && entry.name === change.name);
|
|
5489
|
+
if (change.operation === "remove") {
|
|
5490
|
+
if (change.value !== void 0) throw new Error("A remove change must not include value.");
|
|
5491
|
+
if (index < 0) throw new Error(`Entity not found: ${change.kind} ${change.name}.`);
|
|
5492
|
+
entries.splice(index, 1);
|
|
5493
|
+
} else {
|
|
5494
|
+
if (!change.value) throw new Error("An upsert change requires value.");
|
|
5495
|
+
if (change.value.name !== void 0 && change.value.name !== change.name) throw new Error("value.name must match name.");
|
|
5496
|
+
const value = mergePatch(index < 0 ? {} : entries[index], { ...change.value, name: change.name });
|
|
5497
|
+
if (index < 0) entries.push(value);
|
|
5498
|
+
else entries[index] = value;
|
|
5499
|
+
}
|
|
5500
|
+
parent[key] = entries;
|
|
5501
|
+
return stringify2(model);
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
// src/services/semantic-retrieval.ts
|
|
5505
|
+
function tokens(text2) {
|
|
5506
|
+
return text2.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
5507
|
+
}
|
|
5508
|
+
function text(value) {
|
|
5509
|
+
return typeof value === "string" ? value : Array.isArray(value) ? value.map(text).join(" ") : "";
|
|
5510
|
+
}
|
|
5511
|
+
function retrieveSemanticSource(source2, question, k) {
|
|
5512
|
+
const nodes = semanticEntities(parseSemanticSource(source2), true);
|
|
5513
|
+
const query = new Set(tokens(question));
|
|
5514
|
+
const score = (node) => {
|
|
5515
|
+
const fields = [
|
|
5516
|
+
[node.name, 8],
|
|
5517
|
+
[node.entity.alias ?? node.entity.term, 6],
|
|
5518
|
+
[node.entity.synonyms, 4],
|
|
5519
|
+
[node.entity.description ?? node.entity.definition ?? node.entity.question, 2]
|
|
5520
|
+
];
|
|
5521
|
+
return fields.reduce((sum, [value, weight]) => {
|
|
5522
|
+
const words = new Set(tokens(text(value)));
|
|
5523
|
+
return sum + [...query].filter((word) => words.has(word)).length * weight;
|
|
5524
|
+
}, 0);
|
|
5525
|
+
};
|
|
5526
|
+
const ranked = nodes.map((node) => ({ ...node, score: score(node), match: "lexical" })).filter((node) => node.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, k);
|
|
5527
|
+
const edges = new Map(nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
5528
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5529
|
+
for (const node of nodes) byName.set(node.name, [...byName.get(node.name) ?? [], node.id]);
|
|
5530
|
+
const link = (a, b) => {
|
|
5531
|
+
if (a === b || !edges.has(b)) return;
|
|
5532
|
+
edges.get(a).add(b);
|
|
5533
|
+
edges.get(b).add(a);
|
|
5534
|
+
};
|
|
5535
|
+
const refKeys = /* @__PURE__ */ new Set([
|
|
5536
|
+
"table",
|
|
5537
|
+
"column",
|
|
5538
|
+
"columns",
|
|
5539
|
+
"references",
|
|
5540
|
+
"measure",
|
|
5541
|
+
"numerator",
|
|
5542
|
+
"denominator",
|
|
5543
|
+
"metrics",
|
|
5544
|
+
"measures",
|
|
5545
|
+
"glossary_refs",
|
|
5546
|
+
"left",
|
|
5547
|
+
"right",
|
|
5548
|
+
"from",
|
|
5549
|
+
"to",
|
|
5550
|
+
"on",
|
|
5551
|
+
"bindings",
|
|
5552
|
+
"scope",
|
|
5553
|
+
"targets",
|
|
5554
|
+
"tables",
|
|
5555
|
+
"grain",
|
|
5556
|
+
"identifiers",
|
|
5557
|
+
"default_time_dimension",
|
|
5558
|
+
"expression",
|
|
5559
|
+
"sql",
|
|
5560
|
+
"on_sql",
|
|
5561
|
+
"predicate",
|
|
5562
|
+
"type_config"
|
|
5563
|
+
]);
|
|
5564
|
+
const refs = (value) => {
|
|
5565
|
+
if (typeof value === "string") return value.match(/[\p{L}_][\p{L}\p{N}_]*(?:\.[\p{L}_][\p{L}\p{N}_]*)?/gu) ?? [];
|
|
5566
|
+
if (Array.isArray(value)) return value.flatMap(refs);
|
|
5567
|
+
if (isObject(value)) return Object.entries(value).flatMap(([key, child]) => refKeys.has(key) ? refs(child) : []);
|
|
5568
|
+
return [];
|
|
5569
|
+
};
|
|
5570
|
+
for (const node of nodes) {
|
|
5571
|
+
if (node.kind === "column") link(node.id, `table:${node.name.split(".")[0]}`);
|
|
5572
|
+
for (const ref of refs(node.entity)) for (const id2 of byName.get(ref) ?? []) link(node.id, id2);
|
|
5573
|
+
}
|
|
5574
|
+
const chosen = new Map(ranked.map((node) => [node.id, node]));
|
|
5575
|
+
for (const node of ranked) {
|
|
5576
|
+
for (const id2 of [...edges.get(node.id)].sort()) {
|
|
5577
|
+
if (!chosen.has(id2)) {
|
|
5578
|
+
const adjacent = nodes.find((node2) => node2.id === id2);
|
|
5579
|
+
chosen.set(id2, { ...adjacent, score: 0, match: "adjacency" });
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
return { results: [...chosen.values()], k, lexical_matches: ranked.length, retrieval: "local_lexical" };
|
|
5584
|
+
}
|
|
5585
|
+
|
|
5586
|
+
// src/tools/query/semantic-model.ts
|
|
5587
|
+
var datamartId = z27.string().trim().min(1).max(256).describe("Datamart ID from list_datamarts.");
|
|
5588
|
+
var source = z27.string().min(1).max(2e6).describe("Complete proposed semantic model as YAML.");
|
|
5589
|
+
var entityKind = z27.enum(["table", "column", "measure", "metric", "join", "entity", "glossary", "saved_query", "instruction", "access_rule"]);
|
|
5590
|
+
var changeInputs = {
|
|
5591
|
+
source: source.optional(),
|
|
5592
|
+
yaml_patch: z27.string().min(1).max(2e6).optional().describe("YAML mapping merged into the pulled model. Named lists merge by name. Other lists replace. Null removes a field. An empty list clears it."),
|
|
5593
|
+
change: z27.object({
|
|
5594
|
+
operation: z27.enum(["upsert", "remove"]),
|
|
5595
|
+
kind: entityKind,
|
|
5596
|
+
name: z27.string().trim().min(1),
|
|
5597
|
+
table: z27.string().trim().min(1).optional().describe("Required for columns. Use the table name and an unqualified column name."),
|
|
5598
|
+
value: z27.record(z27.unknown()).optional().describe("Fields to merge for upsert. Omit for remove.")
|
|
5599
|
+
}).strict().optional().describe("One named entity change. Supply exactly one of source, yaml_patch or change.")
|
|
5600
|
+
};
|
|
5601
|
+
var expectedHash = z27.string().trim().min(1).nullable().optional().describe("Stored content_hash from the reviewed proposal. Null explicitly means the first apply. Required when dry_run is false.");
|
|
5602
|
+
function errorCode(error) {
|
|
5603
|
+
const nested = isObject(error.details) && isObject(error.details.error) ? error.details.error : void 0;
|
|
5604
|
+
return typeof nested?.code === "string" ? nested.code : error.code;
|
|
5605
|
+
}
|
|
5606
|
+
function failure(error) {
|
|
5607
|
+
if (error instanceof DatabrainApiError) {
|
|
5608
|
+
if (error.status === 409 || error.status === 422) {
|
|
5609
|
+
return toolError(stringify({
|
|
5610
|
+
status: error.status,
|
|
5611
|
+
code: errorCode(error),
|
|
5612
|
+
reason: error.message,
|
|
5613
|
+
details: error.details,
|
|
5614
|
+
recovery: error.status === 409 ? "Pull the current model and review a new proposal before applying." : "Correct the validation errors and propose the change again."
|
|
5615
|
+
}));
|
|
5616
|
+
}
|
|
5617
|
+
return error.toToolResponse();
|
|
5618
|
+
}
|
|
5619
|
+
return toolError(`Semantic model error: ${error instanceof Error ? error.message : String(error)}`);
|
|
5620
|
+
}
|
|
5621
|
+
async function candidate(api, id2, input) {
|
|
5622
|
+
if ([input.source, input.yaml_patch, input.change].filter((value) => value !== void 0).length !== 1) {
|
|
5623
|
+
throw new Error("Supply exactly one of source, yaml_patch or change.");
|
|
5624
|
+
}
|
|
5625
|
+
let stored;
|
|
5626
|
+
try {
|
|
5627
|
+
stored = await api.pull(id2);
|
|
5628
|
+
} catch (error) {
|
|
5629
|
+
if (!(error instanceof DatabrainApiError && error.status === 404 && errorCode(error) === "layer_not_found" && input.source !== void 0)) throw error;
|
|
5630
|
+
}
|
|
5631
|
+
const hash = stored?.content_hash ?? null;
|
|
5632
|
+
if (input.expected_content_hash !== void 0 && input.expected_content_hash !== hash) {
|
|
5633
|
+
throw new DatabrainApiError(409, "content_hash_mismatch", "The model changed since the proposal was reviewed.", { current_hash: hash });
|
|
5634
|
+
}
|
|
5635
|
+
return {
|
|
5636
|
+
source: input.source ?? changeSemanticSource(stored.source, input),
|
|
5637
|
+
expected_content_hash: input.expected_content_hash === void 0 ? hash : input.expected_content_hash
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
async function proposal(api, id2, input) {
|
|
5641
|
+
const next = await candidate(api, id2, input);
|
|
5642
|
+
const validation = await api.validate(id2, next.source);
|
|
5643
|
+
const plan = validation.ok ? await api.plan(id2, next.source, next.expected_content_hash) : null;
|
|
5644
|
+
return { ...next, validation, errors: validation.errors, plan, dry_run: true };
|
|
5645
|
+
}
|
|
5646
|
+
function registerSearchSemanticModel(server, client) {
|
|
5647
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5648
|
+
server.tool(
|
|
5649
|
+
"search_semantic_model",
|
|
5650
|
+
"Find semantic model entities relevant to a question. Ranks names, aliases, synonyms and descriptions, then includes related entities. Uses the stored model and never writes.",
|
|
5651
|
+
{ datamartId, question: z27.string().trim().min(1).max(1e4), k: z27.number().int().min(1).max(100).default(10) },
|
|
5652
|
+
READ_ONLY,
|
|
5653
|
+
async ({ datamartId: datamartId2, question, k }) => {
|
|
5654
|
+
try {
|
|
5655
|
+
try {
|
|
5656
|
+
return toolSuccess(stringify(await api.retrieve(datamartId2, question, k)));
|
|
5657
|
+
} catch (error) {
|
|
5658
|
+
if (!(error instanceof DatabrainApiError && [404, 405, 501].includes(error.status))) throw error;
|
|
5659
|
+
if (["datamart_not_found", "layer_not_found", "forbidden", "unauthorized"].includes(errorCode(error))) throw error;
|
|
5660
|
+
}
|
|
5661
|
+
const stored = await api.pull(datamartId2);
|
|
5662
|
+
return toolSuccess(stringify({ ...retrieveSemanticSource(stored.source, question, k), content_hash: stored.content_hash }));
|
|
5663
|
+
} catch (error) {
|
|
5664
|
+
return failure(error);
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
);
|
|
5668
|
+
}
|
|
5669
|
+
function registerGetSemanticEntity(server, client) {
|
|
5670
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5671
|
+
server.tool(
|
|
5672
|
+
"get_semantic_entity",
|
|
5673
|
+
"Read one entity by kind and exact name from the stored semantic model. For a column, use table.column. Returns the stored content hash for change review.",
|
|
5674
|
+
{ datamartId, kind: entityKind, name: z27.string().trim().min(1) },
|
|
5675
|
+
READ_ONLY,
|
|
5676
|
+
async ({ datamartId: datamartId2, kind, name }) => {
|
|
5677
|
+
try {
|
|
5678
|
+
const stored = await api.pull(datamartId2);
|
|
5679
|
+
const found = semanticEntities(parseSemanticSource(stored.source)).filter((entity) => entity.kind === kind && entity.name === name);
|
|
5680
|
+
if (found.length !== 1) return toolError(`Expected one ${kind} named ${name}; found ${found.length}. Use search_semantic_model to find its exact name.`);
|
|
5681
|
+
return toolSuccess(stringify({ ...found[0], content_hash: stored.content_hash }));
|
|
5682
|
+
} catch (error) {
|
|
5683
|
+
return failure(error);
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
);
|
|
5687
|
+
}
|
|
5688
|
+
function registerProposeSemanticChange(server, client) {
|
|
5689
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5690
|
+
server.tool(
|
|
5691
|
+
"propose_semantic_change",
|
|
5692
|
+
"Prepare a semantic model change from complete YAML, a YAML patch or one structured change. Returns validation errors, the server plan, candidate YAML and the stored hash. Never writes.",
|
|
5693
|
+
{ datamartId, ...changeInputs },
|
|
5694
|
+
READ_ONLY,
|
|
5695
|
+
async (params) => {
|
|
5696
|
+
try {
|
|
5697
|
+
return toolSuccess(stringify(await proposal(api, params.datamartId, params)));
|
|
5698
|
+
} catch (error) {
|
|
5699
|
+
return failure(error);
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
);
|
|
5703
|
+
}
|
|
5704
|
+
function registerApplySemanticChange(server, client) {
|
|
5705
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5706
|
+
server.tool(
|
|
5707
|
+
"apply_semantic_change",
|
|
5708
|
+
"Preview a semantic change by default. Setting dry_run to false applies it live and can remove entities. Requires the reviewed content hash and confirm set to APPLY_TO_PRODUCTION supplied by the user.",
|
|
5709
|
+
{
|
|
5710
|
+
datamartId,
|
|
5711
|
+
...changeInputs,
|
|
5712
|
+
dry_run: z27.boolean().default(true),
|
|
5713
|
+
expected_content_hash: expectedHash,
|
|
5714
|
+
confirm: z27.string().optional().describe("User supplied APPLY_TO_PRODUCTION after reviewing the plan. Never invent confirmation.")
|
|
5715
|
+
},
|
|
5716
|
+
DESTRUCTIVE,
|
|
5717
|
+
async (params) => {
|
|
5718
|
+
try {
|
|
5719
|
+
if (params.dry_run !== false) return toolSuccess(stringify(await proposal(api, params.datamartId, params)));
|
|
5720
|
+
const refusal = validateProductionApply(params);
|
|
5721
|
+
if (refusal) return toolError(refusal);
|
|
5722
|
+
if (params.expected_content_hash === void 0) return toolError("expected_content_hash is required. Use the stored hash from the reviewed proposal, or null for the first apply.");
|
|
5723
|
+
const next = await candidate(api, params.datamartId, params);
|
|
5724
|
+
const validation = await api.validate(params.datamartId, next.source);
|
|
5725
|
+
if (!validation.ok) return failure(new DatabrainApiError(422, "validation_failed", "Apply refused because the model does not validate.", { validation }));
|
|
5726
|
+
return toolSuccess(stringify(await api.apply(params.datamartId, next.source, next.expected_content_hash)));
|
|
5727
|
+
} catch (error) {
|
|
5728
|
+
return failure(error);
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
);
|
|
5732
|
+
}
|
|
5733
|
+
function registerEvalSemanticModel(server, client) {
|
|
5734
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5735
|
+
server.tool(
|
|
5736
|
+
"eval_semantic_model",
|
|
5737
|
+
"Evaluate saved queries and optional questions against a proposed or stored semantic model. Returns the pass rate and each verdict with its reason. Runs warehouse queries without changing the model.",
|
|
5738
|
+
{ datamartId, source: source.optional(), questions: z27.array(z27.union([z27.string().trim().min(1), z27.object({
|
|
5739
|
+
name: z27.string().optional(),
|
|
5740
|
+
question: z27.string().min(1),
|
|
5741
|
+
sql: z27.string().optional(),
|
|
5742
|
+
expected_columns: z27.array(z27.string()).optional(),
|
|
5743
|
+
expected_empty: z27.boolean().optional()
|
|
5744
|
+
}).strict()])).max(100).optional() },
|
|
5745
|
+
READ_ONLY,
|
|
5746
|
+
async ({ datamartId: datamartId2, source: source2, questions }) => {
|
|
5747
|
+
const shaped = questions?.map((entry) => typeof entry === "string" ? { question: entry } : entry);
|
|
5748
|
+
try {
|
|
5749
|
+
return toolSuccess(stringify(await api.evaluate(datamartId2, { source: source2, questions: shaped })));
|
|
5750
|
+
} catch (error) {
|
|
5751
|
+
return failure(error);
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
);
|
|
5755
|
+
}
|
|
5756
|
+
function registerSemanticReadiness(server, client) {
|
|
5757
|
+
const api = createServiceBundle(client).semanticModelApi;
|
|
5758
|
+
server.tool(
|
|
5759
|
+
"semantic_readiness",
|
|
5760
|
+
"Read the semantic readiness number, verdict and open checks for a stored model. Uses the server result without changing the model.",
|
|
5761
|
+
{ datamartId },
|
|
5762
|
+
READ_ONLY,
|
|
5763
|
+
async ({ datamartId: datamartId2 }) => {
|
|
5764
|
+
try {
|
|
5765
|
+
return toolSuccess(stringify(await api.readiness(datamartId2)));
|
|
5766
|
+
} catch (error) {
|
|
5767
|
+
return failure(error);
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
5772
|
+
|
|
5773
|
+
// src/tools/query/update-semantic-layer.ts
|
|
5774
|
+
import { z as z28 } from "zod";
|
|
5775
|
+
var columnSchema2 = z28.object({
|
|
5776
|
+
name: z28.string().describe("Column name (must exist in the datamart)"),
|
|
5777
|
+
description: z28.string().optional().describe("Business-friendly description of what this column represents"),
|
|
5778
|
+
synonyms: z28.array(z28.string()).optional().describe(
|
|
5325
5779
|
'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
|
|
5326
5780
|
),
|
|
5327
|
-
miscellaneousInfo:
|
|
5328
|
-
columnType:
|
|
5781
|
+
miscellaneousInfo: z28.string().optional().describe("Additional context such as valid values, units, or caveats"),
|
|
5782
|
+
columnType: z28.enum([
|
|
5329
5783
|
"String",
|
|
5330
5784
|
"Long String",
|
|
5331
5785
|
"String (Custom)",
|
|
@@ -5340,21 +5794,21 @@ var columnSchema2 = z27.object({
|
|
|
5340
5794
|
// The public PUT accepts these three as well (docs: Update Semantic Layer).
|
|
5341
5795
|
// They were missing here, and zod strips unknown keys, so a caller who set
|
|
5342
5796
|
// `isIdentifier: true` got a success message and a row with `false`.
|
|
5343
|
-
columnTypeConfig:
|
|
5344
|
-
isIdentifier:
|
|
5345
|
-
isNotIndexed:
|
|
5797
|
+
columnTypeConfig: z28.union([z28.record(z28.string(), z28.string()), z28.string(), z28.object({ lowerLimit: z28.number(), upperLimit: z28.number() }), z28.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
|
|
5798
|
+
isIdentifier: z28.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
|
|
5799
|
+
isNotIndexed: z28.boolean().optional().describe("Exclude the column from AI indexing.")
|
|
5346
5800
|
});
|
|
5347
|
-
var tableSchema2 =
|
|
5348
|
-
name:
|
|
5349
|
-
schemaName:
|
|
5350
|
-
description:
|
|
5801
|
+
var tableSchema2 = z28.object({
|
|
5802
|
+
name: z28.string().describe("Table name (must exist in the datamart)"),
|
|
5803
|
+
schemaName: z28.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
|
|
5804
|
+
description: z28.string().optional().describe(
|
|
5351
5805
|
"Business-friendly description of what this table represents"
|
|
5352
5806
|
),
|
|
5353
|
-
synonyms:
|
|
5354
|
-
miscellaneousInfo:
|
|
5807
|
+
synonyms: z28.array(z28.string()).optional().describe("Alternative names for this table"),
|
|
5808
|
+
miscellaneousInfo: z28.string().optional().describe(
|
|
5355
5809
|
"Additional context such as how the table relates to other tables"
|
|
5356
5810
|
),
|
|
5357
|
-
columns:
|
|
5811
|
+
columns: z28.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
|
|
5358
5812
|
});
|
|
5359
5813
|
function columnKey(tableName, columnName) {
|
|
5360
5814
|
return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
|
|
@@ -5431,13 +5885,13 @@ function registerUpdateSemanticLayer(server, client) {
|
|
|
5431
5885
|
"update_semantic_layer",
|
|
5432
5886
|
"Populate or update the semantic layer for a datamart. Add table/column descriptions, synonyms, context. Required before ask_question works. Auto-generate descriptions from column names when empty.",
|
|
5433
5887
|
{
|
|
5434
|
-
datamartName:
|
|
5888
|
+
datamartName: z28.string().optional().describe(
|
|
5435
5889
|
"Name of the datamart to update. Omit to list available datamarts."
|
|
5436
5890
|
),
|
|
5437
|
-
tables:
|
|
5891
|
+
tables: z28.array(tableSchema2).optional().describe(
|
|
5438
5892
|
"Table and column definitions to update. Only include tables/columns you want to change."
|
|
5439
5893
|
),
|
|
5440
|
-
feedback:
|
|
5894
|
+
feedback: z28.string().optional().describe(
|
|
5441
5895
|
'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
|
|
5442
5896
|
)
|
|
5443
5897
|
},
|
|
@@ -5499,7 +5953,7 @@ You can now:
|
|
|
5499
5953
|
}
|
|
5500
5954
|
|
|
5501
5955
|
// src/tools/query/semantic-generation.ts
|
|
5502
|
-
import { z as
|
|
5956
|
+
import { z as z29 } from "zod";
|
|
5503
5957
|
function nonEmptyString(value) {
|
|
5504
5958
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
5505
5959
|
}
|
|
@@ -5531,9 +5985,9 @@ async function resolveDatamartFromWorkspace(params) {
|
|
|
5531
5985
|
});
|
|
5532
5986
|
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
5533
5987
|
const selected = resolution.selected;
|
|
5534
|
-
const
|
|
5988
|
+
const datamartId2 = workspaceDatamartIdFrom(selected);
|
|
5535
5989
|
const workspaceDatamartName = workspaceDatamartNameFrom(selected);
|
|
5536
|
-
if (!
|
|
5990
|
+
if (!datamartId2) {
|
|
5537
5991
|
return {
|
|
5538
5992
|
ok: false,
|
|
5539
5993
|
response: toolError(
|
|
@@ -5551,8 +6005,8 @@ async function resolveDatamartFromWorkspace(params) {
|
|
|
5551
6005
|
}
|
|
5552
6006
|
return {
|
|
5553
6007
|
ok: true,
|
|
5554
|
-
datamartName: workspaceDatamartName || params.datamartName ||
|
|
5555
|
-
datamartId,
|
|
6008
|
+
datamartName: workspaceDatamartName || params.datamartName || datamartId2,
|
|
6009
|
+
datamartId: datamartId2,
|
|
5556
6010
|
workspaceName: resolution.name
|
|
5557
6011
|
};
|
|
5558
6012
|
}
|
|
@@ -5584,8 +6038,8 @@ async function resolveDatamart(params) {
|
|
|
5584
6038
|
toolName: params.toolName
|
|
5585
6039
|
});
|
|
5586
6040
|
if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
|
|
5587
|
-
const
|
|
5588
|
-
if (!
|
|
6041
|
+
const datamartId2 = datamartIdFrom(resolution.selected);
|
|
6042
|
+
if (!datamartId2) {
|
|
5589
6043
|
return {
|
|
5590
6044
|
ok: false,
|
|
5591
6045
|
response: toolError(
|
|
@@ -5596,7 +6050,7 @@ async function resolveDatamart(params) {
|
|
|
5596
6050
|
return {
|
|
5597
6051
|
ok: true,
|
|
5598
6052
|
datamartName: resolution.name,
|
|
5599
|
-
datamartId
|
|
6053
|
+
datamartId: datamartId2
|
|
5600
6054
|
};
|
|
5601
6055
|
}
|
|
5602
6056
|
function registerStartSemanticLayerGeneration(server, client) {
|
|
@@ -5605,9 +6059,9 @@ function registerStartSemanticLayerGeneration(server, client) {
|
|
|
5605
6059
|
"start_semantic_layer_generation",
|
|
5606
6060
|
"Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
|
|
5607
6061
|
{
|
|
5608
|
-
datamartName:
|
|
5609
|
-
workspaceName:
|
|
5610
|
-
datamartId:
|
|
6062
|
+
datamartName: z29.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
6063
|
+
workspaceName: z29.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
6064
|
+
datamartId: z29.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
5611
6065
|
},
|
|
5612
6066
|
WRITE_CREATE,
|
|
5613
6067
|
async (params) => {
|
|
@@ -5649,9 +6103,9 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
5649
6103
|
"get_semantic_layer_generation_status",
|
|
5650
6104
|
"Get semantic-layer auto-generation job status for a datamart.",
|
|
5651
6105
|
{
|
|
5652
|
-
datamartName:
|
|
5653
|
-
workspaceName:
|
|
5654
|
-
datamartId:
|
|
6106
|
+
datamartName: z29.string().optional().describe("Datamart name. Omit to list available datamarts"),
|
|
6107
|
+
workspaceName: z29.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
|
|
6108
|
+
datamartId: z29.string().optional().describe("Datamart UUID. Use only when already known.")
|
|
5655
6109
|
},
|
|
5656
6110
|
READ_ONLY,
|
|
5657
6111
|
async (params) => {
|
|
@@ -5688,10 +6142,10 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
|
|
|
5688
6142
|
}
|
|
5689
6143
|
|
|
5690
6144
|
// src/tools/widgets/list-widgets.ts
|
|
5691
|
-
import { z as
|
|
6145
|
+
import { z as z30 } from "zod";
|
|
5692
6146
|
function widgetReferenceId(widget) {
|
|
5693
|
-
for (const
|
|
5694
|
-
if (typeof
|
|
6147
|
+
for (const candidate2 of [widget.externalMetricId, widget.id, widget._id, widget.metricId]) {
|
|
6148
|
+
if (typeof candidate2 === "string" && candidate2.trim().length > 0) return candidate2.trim();
|
|
5695
6149
|
}
|
|
5696
6150
|
return void 0;
|
|
5697
6151
|
}
|
|
@@ -5701,10 +6155,10 @@ function registerListWidgets(server, client) {
|
|
|
5701
6155
|
"list_widgets",
|
|
5702
6156
|
"List widgets on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Returns each widget's externalMetricId (uuid, used for update/remove), metricId (slug), name, published state, and creator identifier.",
|
|
5703
6157
|
{
|
|
5704
|
-
clientId:
|
|
5705
|
-
dashboardName:
|
|
5706
|
-
workspaceName:
|
|
5707
|
-
embedId:
|
|
6158
|
+
clientId: z30.string().describe("Client/tenant identifier for this dashboard"),
|
|
6159
|
+
dashboardName: z30.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6160
|
+
workspaceName: z30.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6161
|
+
embedId: z30.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
|
|
5708
6162
|
},
|
|
5709
6163
|
READ_ONLY,
|
|
5710
6164
|
async (params) => {
|
|
@@ -5768,29 +6222,29 @@ function registerListWidgets(server, client) {
|
|
|
5768
6222
|
}
|
|
5769
6223
|
|
|
5770
6224
|
// src/tools/widgets/add-widget.ts
|
|
5771
|
-
import { z as
|
|
6225
|
+
import { z as z31 } from "zod";
|
|
5772
6226
|
function registerAddWidget(server, client) {
|
|
5773
6227
|
const services = createServiceBundle(client);
|
|
5774
6228
|
server.tool(
|
|
5775
6229
|
"add_widget",
|
|
5776
6230
|
"Add a widget to a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Cloning via `templateMetricId` is the recommended path \u2014 query, chart config, and datasource are all inherited. Data app and datasource auto-resolve; never ask the user for them.",
|
|
5777
6231
|
{
|
|
5778
|
-
clientId:
|
|
5779
|
-
dashboardName:
|
|
5780
|
-
workspaceName:
|
|
5781
|
-
embedId:
|
|
5782
|
-
dataAppName:
|
|
5783
|
-
templateMetricId:
|
|
5784
|
-
name:
|
|
5785
|
-
metricId:
|
|
5786
|
-
query:
|
|
5787
|
-
chartOptions:
|
|
5788
|
-
description:
|
|
5789
|
-
companyIntegrationId:
|
|
5790
|
-
integrationName:
|
|
5791
|
-
isPublished:
|
|
5792
|
-
body:
|
|
5793
|
-
widgets:
|
|
6232
|
+
clientId: z31.string().describe("Client/tenant identifier (widget is created in their scope)"),
|
|
6233
|
+
dashboardName: z31.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6234
|
+
workspaceName: z31.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6235
|
+
embedId: z31.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6236
|
+
dataAppName: z31.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
|
|
6237
|
+
templateMetricId: z31.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
|
|
6238
|
+
name: z31.string().optional().describe("Widget name (overrides template)"),
|
|
6239
|
+
metricId: z31.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
|
|
6240
|
+
query: z31.string().optional().describe("SQL query body (overrides template)"),
|
|
6241
|
+
chartOptions: z31.record(z31.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
|
|
6242
|
+
description: z31.string().optional(),
|
|
6243
|
+
companyIntegrationId: z31.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
|
|
6244
|
+
integrationName: z31.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
|
|
6245
|
+
isPublished: z31.boolean().optional().describe("Publish immediately (default true)"),
|
|
6246
|
+
body: z31.record(z31.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
|
|
6247
|
+
widgets: z31.array(z31.record(z31.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
|
|
5794
6248
|
},
|
|
5795
6249
|
WRITE_CREATE,
|
|
5796
6250
|
async (params) => {
|
|
@@ -5938,20 +6392,20 @@ Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust
|
|
|
5938
6392
|
}
|
|
5939
6393
|
|
|
5940
6394
|
// src/tools/widgets/update-widget.ts
|
|
5941
|
-
import { z as
|
|
6395
|
+
import { z as z32 } from "zod";
|
|
5942
6396
|
function registerUpdateWidget(server, client) {
|
|
5943
6397
|
const services = createServiceBundle(client);
|
|
5944
6398
|
server.tool(
|
|
5945
6399
|
"update_widget",
|
|
5946
6400
|
"Update a widget on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId` + `set`. Data app auto-resolves from the dashboard.",
|
|
5947
6401
|
{
|
|
5948
|
-
externalMetricId:
|
|
5949
|
-
clientId:
|
|
5950
|
-
dashboardName:
|
|
5951
|
-
workspaceName:
|
|
5952
|
-
embedId:
|
|
5953
|
-
dataAppName:
|
|
5954
|
-
set:
|
|
6402
|
+
externalMetricId: z32.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
|
|
6403
|
+
clientId: z32.string().describe("Client/tenant identifier"),
|
|
6404
|
+
dashboardName: z32.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6405
|
+
workspaceName: z32.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6406
|
+
embedId: z32.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6407
|
+
dataAppName: z32.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
|
|
6408
|
+
set: z32.record(z32.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
|
|
5955
6409
|
},
|
|
5956
6410
|
WRITE_IDEMPOTENT,
|
|
5957
6411
|
async (params) => {
|
|
@@ -6014,19 +6468,19 @@ Use \`list_widgets\` to verify.`
|
|
|
6014
6468
|
}
|
|
6015
6469
|
|
|
6016
6470
|
// src/tools/widgets/remove-widget.ts
|
|
6017
|
-
import { z as
|
|
6471
|
+
import { z as z33 } from "zod";
|
|
6018
6472
|
function registerRemoveWidget(server, client) {
|
|
6019
6473
|
const services = createServiceBundle(client);
|
|
6020
6474
|
server.tool(
|
|
6021
6475
|
"remove_widget",
|
|
6022
6476
|
"Remove a widget from a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId`. Per-client soft delete \u2014 the widget stays on the template dashboard and on other customers' dashboards.",
|
|
6023
6477
|
{
|
|
6024
|
-
externalMetricId:
|
|
6025
|
-
clientId:
|
|
6026
|
-
dashboardName:
|
|
6027
|
-
workspaceName:
|
|
6028
|
-
embedId:
|
|
6029
|
-
dataAppName:
|
|
6478
|
+
externalMetricId: z33.string().describe("Internal uuid of the widget (externalMetrics.id)"),
|
|
6479
|
+
clientId: z33.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
|
|
6480
|
+
dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6481
|
+
workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6482
|
+
embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6483
|
+
dataAppName: z33.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
|
|
6030
6484
|
},
|
|
6031
6485
|
DESTRUCTIVE,
|
|
6032
6486
|
async (params) => {
|
|
@@ -6082,21 +6536,21 @@ Note: this is a per-client soft delete. The widget remains available for other c
|
|
|
6082
6536
|
}
|
|
6083
6537
|
|
|
6084
6538
|
// src/tools/widgets/generate-widget.ts
|
|
6085
|
-
import { z as
|
|
6539
|
+
import { z as z34 } from "zod";
|
|
6086
6540
|
function registerGenerateWidget(server, client) {
|
|
6087
6541
|
const services = createServiceBundle(client);
|
|
6088
6542
|
const tool = server.tool(
|
|
6089
6543
|
"generate_widget",
|
|
6090
6544
|
"AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft to pass to `add_widget`. Non-technical flow: `clientId` + `dashboardName` (+ `workspaceName` if ambiguous); data app and datasource auto-resolve. Never ask the user for data app, datasource, or integration.",
|
|
6091
6545
|
{
|
|
6092
|
-
clientId:
|
|
6093
|
-
userInputs:
|
|
6094
|
-
dashboardName:
|
|
6095
|
-
workspaceName:
|
|
6096
|
-
embedId:
|
|
6097
|
-
dataAppName:
|
|
6098
|
-
integrationId:
|
|
6099
|
-
integrationName:
|
|
6546
|
+
clientId: z34.string().describe("Client/tenant identifier"),
|
|
6547
|
+
userInputs: z34.record(z34.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
|
|
6548
|
+
dashboardName: z34.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
|
|
6549
|
+
workspaceName: z34.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
|
|
6550
|
+
embedId: z34.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
|
|
6551
|
+
dataAppName: z34.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
|
|
6552
|
+
integrationId: z34.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
|
|
6553
|
+
integrationName: z34.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
|
|
6100
6554
|
},
|
|
6101
6555
|
READ_ONLY,
|
|
6102
6556
|
async (params) => {
|
|
@@ -6181,9 +6635,9 @@ function registerGenerateWidget(server, client) {
|
|
|
6181
6635
|
}
|
|
6182
6636
|
|
|
6183
6637
|
// src/tools/widgets/create-or-update-metric-from-query.ts
|
|
6184
|
-
import { z as
|
|
6185
|
-
var modeSchema =
|
|
6186
|
-
var conflictStrategySchema =
|
|
6638
|
+
import { z as z35 } from "zod";
|
|
6639
|
+
var modeSchema = z35.enum(["dry_run", "apply"]).default("dry_run");
|
|
6640
|
+
var conflictStrategySchema = z35.enum(["fail", "update", "create_new"]).default("fail");
|
|
6187
6641
|
function makePlanText(plan) {
|
|
6188
6642
|
return JSON.stringify(plan, null, 2);
|
|
6189
6643
|
}
|
|
@@ -6200,22 +6654,22 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6200
6654
|
"[EMBEDDING] Create or update a production metric/widget on a customer dashboard from validated SQL. Requires clientId (multi-tenant embed). Defaults to dry-run; apply requires explicit confirmation.",
|
|
6201
6655
|
{
|
|
6202
6656
|
mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
|
|
6203
|
-
confirm:
|
|
6657
|
+
confirm: z35.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6204
6658
|
conflictStrategy: conflictStrategySchema.describe("What to do if metricId already exists on the target dashboard"),
|
|
6205
|
-
clientId:
|
|
6206
|
-
workspaceName:
|
|
6207
|
-
dashboardName:
|
|
6208
|
-
embedId:
|
|
6209
|
-
dataAppName:
|
|
6210
|
-
name:
|
|
6211
|
-
metricId:
|
|
6212
|
-
query:
|
|
6213
|
-
description:
|
|
6214
|
-
chartOptions:
|
|
6215
|
-
companyIntegrationId:
|
|
6216
|
-
integrationName:
|
|
6217
|
-
isPublished:
|
|
6218
|
-
requestedBy:
|
|
6659
|
+
clientId: z35.string().describe("Client/tenant identifier for the dashboard widget scope"),
|
|
6660
|
+
workspaceName: z35.string().describe("Target workspace name"),
|
|
6661
|
+
dashboardName: z35.string().optional().describe("Dashboard name to attach to"),
|
|
6662
|
+
embedId: z35.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6663
|
+
dataAppName: z35.string().optional().describe("Optional data app override"),
|
|
6664
|
+
name: z35.string().describe("Metric/widget display name"),
|
|
6665
|
+
metricId: z35.string().describe("Stable metric slug to create or update"),
|
|
6666
|
+
query: z35.string().describe("SQL query already validated against the warehouse"),
|
|
6667
|
+
description: z35.string().optional().describe("Metric description"),
|
|
6668
|
+
chartOptions: z35.record(z35.unknown()).optional().describe("Chart configuration JSON"),
|
|
6669
|
+
companyIntegrationId: z35.string().optional().describe("Datasource UUID override"),
|
|
6670
|
+
integrationName: z35.string().optional().describe("Datasource display name override"),
|
|
6671
|
+
isPublished: z35.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
|
|
6672
|
+
requestedBy: z35.string().optional().describe("Human operator/request context to store on the metric when supported")
|
|
6219
6673
|
},
|
|
6220
6674
|
WRITE_IDEMPOTENT,
|
|
6221
6675
|
async (params) => {
|
|
@@ -6375,8 +6829,8 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
|
|
|
6375
6829
|
}
|
|
6376
6830
|
|
|
6377
6831
|
// src/tools/widgets/create-workspace-metric.ts
|
|
6378
|
-
import { z as
|
|
6379
|
-
var modeSchema2 =
|
|
6832
|
+
import { z as z36 } from "zod";
|
|
6833
|
+
var modeSchema2 = z36.enum(["dry_run", "apply"]).default("dry_run");
|
|
6380
6834
|
function registerCreateWorkspaceMetric(server, client) {
|
|
6381
6835
|
server.tool(
|
|
6382
6836
|
"create_workspace_metric",
|
|
@@ -6385,16 +6839,18 @@ function registerCreateWorkspaceMetric(server, client) {
|
|
|
6385
6839
|
mode: modeSchema2.describe(
|
|
6386
6840
|
"dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
|
|
6387
6841
|
),
|
|
6388
|
-
confirm:
|
|
6389
|
-
workspaceName:
|
|
6390
|
-
name:
|
|
6391
|
-
query:
|
|
6392
|
-
|
|
6842
|
+
confirm: z36.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
6843
|
+
workspaceName: z36.string().describe("Target workspace name"),
|
|
6844
|
+
name: z36.string().describe("Metric display name"),
|
|
6845
|
+
query: z36.string().describe(
|
|
6846
|
+
"SQL query to validate and store. In a TABLE-tenancy workspace never compare the client column to a fixed client value; use `client_id_variable` so each client sees only its own rows (the backend refuses it with TENANT_KEY_LITERAL)."
|
|
6847
|
+
),
|
|
6848
|
+
metricId: z36.string().optional().describe(
|
|
6393
6849
|
"Stable metric slug. Pass an existing slug to UPDATE that metric in place. Omit to create \u2014 the slug is derived deterministically from the name (no random/uniqueness suffix), so re-running with the same name upserts the same metric rather than creating a duplicate."
|
|
6394
6850
|
),
|
|
6395
|
-
description:
|
|
6396
|
-
datasourceId:
|
|
6397
|
-
isPublished:
|
|
6851
|
+
description: z36.string().optional().describe("Metric description"),
|
|
6852
|
+
datasourceId: z36.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
|
|
6853
|
+
isPublished: z36.boolean().optional().describe(
|
|
6398
6854
|
"Publish after apply. Defaults to false. Applies to creates; on updates the existing publish state is preserved."
|
|
6399
6855
|
)
|
|
6400
6856
|
},
|
|
@@ -6450,25 +6906,25 @@ function registerCreateWorkspaceMetric(server, client) {
|
|
|
6450
6906
|
}
|
|
6451
6907
|
|
|
6452
6908
|
// src/tools/widgets/validate-metric-spec.ts
|
|
6453
|
-
import { z as
|
|
6909
|
+
import { z as z37 } from "zod";
|
|
6454
6910
|
function registerValidateMetricSpec(server, client) {
|
|
6455
6911
|
const services = createServiceBundle(client);
|
|
6456
6912
|
server.tool(
|
|
6457
6913
|
"validate_metric_spec",
|
|
6458
6914
|
"Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
|
|
6459
6915
|
{
|
|
6460
|
-
clientId:
|
|
6461
|
-
workspaceName:
|
|
6462
|
-
dashboardName:
|
|
6463
|
-
embedId:
|
|
6464
|
-
dataAppName:
|
|
6465
|
-
name:
|
|
6466
|
-
metricId:
|
|
6467
|
-
query:
|
|
6468
|
-
description:
|
|
6469
|
-
chartOptions:
|
|
6470
|
-
companyIntegrationId:
|
|
6471
|
-
integrationName:
|
|
6916
|
+
clientId: z37.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
|
|
6917
|
+
workspaceName: z37.string().describe("Target production-backed workspace name"),
|
|
6918
|
+
dashboardName: z37.string().optional().describe("Dashboard name to attach to"),
|
|
6919
|
+
embedId: z37.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
6920
|
+
dataAppName: z37.string().optional().describe("Optional data app override"),
|
|
6921
|
+
name: z37.string().describe("Metric/widget display name"),
|
|
6922
|
+
metricId: z37.string().describe("Stable metric slug to create or update"),
|
|
6923
|
+
query: z37.string().describe("SQL query already validated against the warehouse"),
|
|
6924
|
+
description: z37.string().optional().describe("Metric description"),
|
|
6925
|
+
chartOptions: z37.record(z37.unknown()).optional().describe("Chart configuration JSON"),
|
|
6926
|
+
companyIntegrationId: z37.string().optional().describe("Datasource UUID override"),
|
|
6927
|
+
integrationName: z37.string().optional().describe("Datasource display name override")
|
|
6472
6928
|
},
|
|
6473
6929
|
READ_ONLY,
|
|
6474
6930
|
async (params) => {
|
|
@@ -6559,20 +7015,20 @@ function registerValidateMetricSpec(server, client) {
|
|
|
6559
7015
|
}
|
|
6560
7016
|
|
|
6561
7017
|
// src/tools/reports/list-scheduled-reports.ts
|
|
6562
|
-
import { z as
|
|
7018
|
+
import { z as z38 } from "zod";
|
|
6563
7019
|
function registerListScheduledReports(server, client) {
|
|
6564
7020
|
const services = createServiceBundle(client);
|
|
6565
7021
|
server.tool(
|
|
6566
7022
|
"list_scheduled_reports",
|
|
6567
7023
|
"List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
|
|
6568
7024
|
{
|
|
6569
|
-
clientId:
|
|
6570
|
-
dashboardName:
|
|
6571
|
-
workspaceName:
|
|
6572
|
-
embedId:
|
|
6573
|
-
userIdentifier:
|
|
6574
|
-
pageNumber:
|
|
6575
|
-
isPagination:
|
|
7025
|
+
clientId: z38.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
|
|
7026
|
+
dashboardName: z38.string().optional().describe("Dashboard name to inspect"),
|
|
7027
|
+
workspaceName: z38.string().optional().describe("Workspace name if dashboardName is ambiguous"),
|
|
7028
|
+
embedId: z38.string().optional().describe("Technical embed id alternative to dashboardName"),
|
|
7029
|
+
userIdentifier: z38.string().optional().describe("Optional creator/user filter"),
|
|
7030
|
+
pageNumber: z38.number().int().positive().optional().describe("Page number when pagination is enabled"),
|
|
7031
|
+
isPagination: z38.boolean().optional().describe("Enable backend pagination")
|
|
6576
7032
|
},
|
|
6577
7033
|
READ_ONLY,
|
|
6578
7034
|
async (params) => {
|
|
@@ -6682,19 +7138,19 @@ function registerWhoami(server, client) {
|
|
|
6682
7138
|
}
|
|
6683
7139
|
|
|
6684
7140
|
// src/tools/custom-chart/scaffold.ts
|
|
6685
|
-
import { z as
|
|
7141
|
+
import { z as z40 } from "zod";
|
|
6686
7142
|
|
|
6687
7143
|
// src/tools/custom-chart/shared.ts
|
|
6688
|
-
import { z as
|
|
7144
|
+
import { z as z39 } from "zod";
|
|
6689
7145
|
var slotInputSchema = slotSchema;
|
|
6690
7146
|
var optionInputSchema = optionSchema;
|
|
6691
|
-
var manifestInputSchema =
|
|
7147
|
+
var manifestInputSchema = z39.record(z39.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
|
|
6692
7148
|
var bundleInputShape = {
|
|
6693
7149
|
manifest: manifestInputSchema.optional(),
|
|
6694
|
-
bundleJs:
|
|
6695
|
-
bundleCss:
|
|
6696
|
-
iconSvg:
|
|
6697
|
-
projectDir:
|
|
7150
|
+
bundleJs: z39.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
|
|
7151
|
+
bundleCss: z39.string().optional().describe("The raw chart.css, kept for provenance."),
|
|
7152
|
+
iconSvg: z39.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
|
|
7153
|
+
projectDir: z39.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
|
|
6698
7154
|
};
|
|
6699
7155
|
function formatProblems(problems) {
|
|
6700
7156
|
if (problems.length === 0) return "No problems.";
|
|
@@ -6761,11 +7217,11 @@ function registerCustomChartScaffold(server, _client) {
|
|
|
6761
7217
|
"custom_chart_scaffold",
|
|
6762
7218
|
"Return the files of a new custom chart project (manifest.json, chart.ts, chart.css, icon.svg, sample data, guide). Nothing is written or persisted; write the files yourself.",
|
|
6763
7219
|
{
|
|
6764
|
-
key:
|
|
6765
|
-
name:
|
|
6766
|
-
description:
|
|
6767
|
-
slots:
|
|
6768
|
-
options:
|
|
7220
|
+
key: z40.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
|
|
7221
|
+
name: z40.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
|
|
7222
|
+
description: z40.string().optional().describe("Optional description, up to 500 characters."),
|
|
7223
|
+
slots: z40.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
|
|
7224
|
+
options: z40.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
|
|
6769
7225
|
},
|
|
6770
7226
|
READ_ONLY,
|
|
6771
7227
|
async (params) => {
|
|
@@ -6857,18 +7313,18 @@ function registerCustomChartValidate(server, client) {
|
|
|
6857
7313
|
}
|
|
6858
7314
|
|
|
6859
7315
|
// src/tools/custom-chart/upload.ts
|
|
6860
|
-
import { z as
|
|
7316
|
+
import { z as z41 } from "zod";
|
|
6861
7317
|
function registerCustomChartUpload(server, client) {
|
|
6862
7318
|
const api = createCustomChartsApi(client);
|
|
6863
7319
|
server.tool(
|
|
6864
7320
|
"custom_chart_upload",
|
|
6865
7321
|
"Upload a built custom chart bundle to the company. Creates the chart (private, version 1) or a new preview version. Defaults to dry_run; apply requires explicit confirmation and can publish in the same call.",
|
|
6866
7322
|
{
|
|
6867
|
-
mode:
|
|
6868
|
-
confirm:
|
|
6869
|
-
publish:
|
|
6870
|
-
name:
|
|
6871
|
-
description:
|
|
7323
|
+
mode: z41.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
|
|
7324
|
+
confirm: z41.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
|
|
7325
|
+
publish: z41.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
|
|
7326
|
+
name: z41.string().optional().describe("Display name override. Defaults to manifest.name."),
|
|
7327
|
+
description: z41.string().optional().describe("Description override. Defaults to manifest.description."),
|
|
6872
7328
|
...bundleInputShape
|
|
6873
7329
|
},
|
|
6874
7330
|
WRITE_CREATE,
|
|
@@ -7009,17 +7465,17 @@ function registerListCustomCharts(server, client) {
|
|
|
7009
7465
|
}
|
|
7010
7466
|
|
|
7011
7467
|
// src/tools/custom-chart/publish.ts
|
|
7012
|
-
import { z as
|
|
7468
|
+
import { z as z42 } from "zod";
|
|
7013
7469
|
function registerPublishCustomChart(server, client) {
|
|
7014
7470
|
const api = createCustomChartsApi(client);
|
|
7015
7471
|
server.tool(
|
|
7016
7472
|
"publish_custom_chart",
|
|
7017
7473
|
"Publish a custom chart version so every builder in the company can use chart type custom:<key>. Defaults to the preview version. Defaults to dry_run; apply requires explicit confirmation.",
|
|
7018
7474
|
{
|
|
7019
|
-
key:
|
|
7020
|
-
versionId:
|
|
7021
|
-
mode:
|
|
7022
|
-
confirm:
|
|
7475
|
+
key: z42.string().describe("The chart key (manifest.id), for example revenue-bars."),
|
|
7476
|
+
versionId: z42.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
|
|
7477
|
+
mode: z42.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
|
|
7478
|
+
confirm: z42.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
|
|
7023
7479
|
},
|
|
7024
7480
|
WRITE_IDEMPOTENT,
|
|
7025
7481
|
async (params) => {
|
|
@@ -7130,6 +7586,12 @@ function registerAllTools(server, client) {
|
|
|
7130
7586
|
registerUpdateSemanticLayer(server, client);
|
|
7131
7587
|
registerStartSemanticLayerGeneration(server, client);
|
|
7132
7588
|
registerGetSemanticLayerGenerationStatus(server, client);
|
|
7589
|
+
registerSearchSemanticModel(server, client);
|
|
7590
|
+
registerGetSemanticEntity(server, client);
|
|
7591
|
+
registerProposeSemanticChange(server, client);
|
|
7592
|
+
registerApplySemanticChange(server, client);
|
|
7593
|
+
registerEvalSemanticModel(server, client);
|
|
7594
|
+
registerSemanticReadiness(server, client);
|
|
7133
7595
|
registerListWidgets(server, client);
|
|
7134
7596
|
registerValidateMetricSpec(server, client);
|
|
7135
7597
|
registerCreateOrUpdateMetricFromQuery(server, client);
|
|
@@ -9024,6 +9486,20 @@ When the same flag is set at multiple levels, the highest precedence wins:
|
|
|
9024
9486
|
|
|
9025
9487
|
// src/resources/content/semantic-layer-guide.ts
|
|
9026
9488
|
var semanticLayerGuideContent = `
|
|
9489
|
+
|
|
9490
|
+
## Semantic models v2
|
|
9491
|
+
|
|
9492
|
+
The v2 source is one YAML document. Use the configured service credential and a datamart ID from \`list_datamarts\`.
|
|
9493
|
+
|
|
9494
|
+
1. \`search_semantic_model\` finds entities by question. \`get_semantic_entity\` reads one entity by kind and name. Column names use table.column.
|
|
9495
|
+
2. \`propose_semantic_change\` takes complete source YAML, a YAML patch or one structured change. It returns candidate source, validation errors, a plan and the stored expected hash without writing.
|
|
9496
|
+
3. \`eval_semantic_model\` evaluates the proposed source. \`semantic_readiness\` returns the server score, verdict and open checks.
|
|
9497
|
+
4. \`apply_semantic_change\` defaults to a dry run. Live apply requires the exact reviewed source, \`expected_content_hash\`, \`dry_run: false\` and user supplied \`confirm: "APPLY_TO_PRODUCTION"\`. Never supply confirmation yourself. Apply changes the live model for all consumers. On a conflict, prepare a fresh proposal and review it again.
|
|
9498
|
+
|
|
9499
|
+
YAML patches merge mappings and named lists by name. Other lists replace. Empty lists clear; null removes a field. Structured changes use operation upsert or remove, kind, name, and value for upsert. Column changes also require table. Patch output normalizes formatting and comments, so review the returned source before apply.
|
|
9500
|
+
|
|
9501
|
+
The legacy maintenance workflow below applies to datamarts that have not moved to v2. Do not call \`update_semantic_layer\` for v2.
|
|
9502
|
+
|
|
9027
9503
|
# Databrain Semantic Layer Guide
|
|
9028
9504
|
|
|
9029
9505
|
## What Is the Semantic Layer?
|
|
@@ -10010,6 +10486,9 @@ If **no datamarts exist**, tell the user they need to create one in the **Databr
|
|
|
10010
10486
|
|
|
10011
10487
|
If **no datasources exist either**, the user must first connect their database in the **Databrain UI**.
|
|
10012
10488
|
|
|
10489
|
+
**For a v2 semantic model**
|
|
10490
|
+
If the datamart uses v2, inspect the question's entities with \`search_semantic_model\` and \`get_semantic_entity\`. Read \`semantic_readiness\` for open checks, then continue to Step 2. If metadata needs correction, use \`propose_semantic_change\`, evaluate its source with \`eval_semantic_model\`, and show the plan. Only apply the reviewed source and expected hash through \`apply_semantic_change\` after the user supplies \`APPLY_TO_PRODUCTION\`. Never use the legacy \`update_semantic_layer\` steps below for v2.
|
|
10491
|
+
|
|
10013
10492
|
**Step 1b \u2014 Verify the Semantic Layer**
|
|
10014
10493
|
After confirming a datamart exists, use \`get_semantic_layer\` to check its tables. The semantic layer is built on the **datamart** (not the raw datasource) \u2014 do NOT use \`discover_datasource_schema\` to populate it.
|
|
10015
10494
|
|
|
@@ -10091,6 +10570,15 @@ If the confidence is low or results seem wrong:
|
|
|
10091
10570
|
// src/prompts/populate-semantic-layer.ts
|
|
10092
10571
|
var populateSemanticLayerPrompt = `You are helping the user populate the semantic layer for a Databrain datamart. The semantic layer provides table/column descriptions, synonyms, and context that the AI needs to convert natural language questions into accurate SQL.
|
|
10093
10572
|
|
|
10573
|
+
**For a v2 semantic model**
|
|
10574
|
+
If \`list_datamarts\` reports \`semanticLayerVersion: "v2"\`, or the selected datamart is already known to use v2, use this workflow instead of the legacy steps below:
|
|
10575
|
+
1. Inspect relevant entities with \`search_semantic_model\` and \`get_semantic_entity\`, using the datamart ID.
|
|
10576
|
+
2. Call \`propose_semantic_change\` with a YAML patch or structured change. Show its errors and plan. Correct errors before continuing.
|
|
10577
|
+
3. Call \`eval_semantic_model\` with the returned source. Show the verdicts and let the user review the candidate.
|
|
10578
|
+
4. Stop for the user's explicit \`APPLY_TO_PRODUCTION\` confirmation. Never supply it yourself.
|
|
10579
|
+
5. Call \`apply_semantic_change\` with the exact reviewed source, its \`expected_content_hash\`, \`dry_run: false\` and the user's confirmation. A conflict requires a new proposal and review.
|
|
10580
|
+
6. Call \`semantic_readiness\` and inspect the changed entity. Do not call \`update_semantic_layer\` for a v2 model.
|
|
10581
|
+
|
|
10094
10582
|
**Why this matters:** Without a populated semantic layer, \`ask_question\` cannot generate SQL \u2014 every query will fail with "out of context." Populating the semantic layer is the critical step between having a datamart and being able to query it.
|
|
10095
10583
|
|
|
10096
10584
|
**Step 1 \u2014 Identify the Datamart**
|
|
@@ -10475,4 +10963,4 @@ export {
|
|
|
10475
10963
|
registerAllResources,
|
|
10476
10964
|
registerAllPrompts
|
|
10477
10965
|
};
|
|
10478
|
-
//# sourceMappingURL=chunk-
|
|
10966
|
+
//# sourceMappingURL=chunk-JSYG7ULR.js.map
|