yamchart 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{advisor-SC64RTZO.js → advisor-O2BRAI4T.js} +24 -14
- package/dist/advisor-O2BRAI4T.js.map +1 -0
- package/dist/{chunk-NXQ6ZO3V.js → chunk-5FHV22X2.js} +6 -6
- package/dist/chunk-5FHV22X2.js.map +1 -0
- package/dist/chunk-FN6R2LAC.js +15442 -0
- package/dist/chunk-FN6R2LAC.js.map +1 -0
- package/dist/chunk-QJ5CPQJK.js +2053 -0
- package/dist/chunk-QJ5CPQJK.js.map +1 -0
- package/dist/{chunk-S7YQXEKM.js → chunk-QUIDZO5G.js} +103 -275
- package/dist/chunk-QUIDZO5G.js.map +1 -0
- package/dist/{chunk-RMIDEBHD.js → chunk-S2CH4HUZ.js} +6 -6
- package/dist/{chunk-UND73EOB.js → chunk-UFDQ3C7Q.js} +473 -4
- package/dist/chunk-UFDQ3C7Q.js.map +1 -0
- package/dist/{chunk-H4L3FNLS.js → chunk-ZBCQNWVN.js} +2 -2
- package/dist/{chunk-RM6MNDVF.js → chunk-ZIY22VO7.js} +192 -11
- package/dist/chunk-ZIY22VO7.js.map +1 -0
- package/dist/{connection-utils-C4FQGBW6.js → connection-utils-FEUWER5I.js} +5 -5
- package/dist/{describe-X75C2VDU.js → describe-MEP72B56.js} +6 -6
- package/dist/{dev-GWXHBBWB.js → dev-LZ4YHNDJ.js} +1315 -73
- package/dist/dev-LZ4YHNDJ.js.map +1 -0
- package/dist/dist-5IFWLWND.js +48 -0
- package/dist/{dist-MX5K2ABB.js → dist-PPAD6KOM.js} +44 -2
- package/dist/{dist-JMLAZUL7.js → dist-XNCED7JW.js} +29 -12
- package/dist/fileFromPath-7TNUU6RI.js +130 -0
- package/dist/fileFromPath-7TNUU6RI.js.map +1 -0
- package/dist/index.js +24 -24
- package/dist/public/assets/DataView-DUCz_96y.js +9 -0
- package/dist/public/assets/{EventManagement-CTuAJ0eF.js → EventManagement-BnmeJDQl.js} +1 -1
- package/dist/public/assets/{ExplorePage-ZJ3zNjNQ.js → ExplorePage-kk4z9ldZ.js} +1 -1
- package/dist/public/assets/{LoginPage-wygea4BI.js → LoginPage-CzaFkkjg.js} +1 -1
- package/dist/public/assets/{PublicViewer-8YqGrVVB.js → PublicViewer-irjxqH6a.js} +1 -1
- package/dist/public/assets/{SetupWizard-DqDBwHrF.js → SetupWizard-ConWIcmy.js} +1 -1
- package/dist/public/assets/{ShareManagement-TAAdI_gY.js → ShareManagement-CP4wdwLR.js} +1 -1
- package/dist/public/assets/SourceDetailView-DZS5518E.js +1 -0
- package/dist/public/assets/{UserManagement-B4kZHyri.js → UserManagement-AubGd9hl.js} +1 -1
- package/dist/public/assets/data-3vtzSuAZ.js +1 -0
- package/dist/public/assets/{index-CfyF2Wf-.css → index-C1X8RW4Z.css} +1 -1
- package/dist/public/assets/{index-BgzSjgIu.js → index-jlfTO7f5.js} +44 -44
- package/dist/public/assets/{index.es-AB-GdGyc.js → index.es-CgnvEWi5.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-ChRx2mOQ.js → jspdf.es.min-Cw5WefMt.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-QNRDS74I.js → query-2H3YOPI2.js} +5 -5
- package/dist/{sample-SKLHBZBU.js → sample-ODUGGSFA.js} +5 -5
- package/dist/{search-4KMETZVX.js → search-IPE4ISFB.js} +6 -6
- package/dist/{semantic-6WKELH5V.js → semantic-K3MYXXJI.js} +3 -2
- package/dist/{semantic-6WKELH5V.js.map → semantic-K3MYXXJI.js.map} +1 -1
- package/dist/{source-resolver-R7WBIL7M.js → source-resolver-HZQLOODU.js} +6 -6
- package/dist/source-resolver-HZQLOODU.js.map +1 -0
- package/dist/{sync-warehouse-UWRNUXE7.js → sync-warehouse-26L6JDSV.js} +10 -10
- package/dist/{tables-V65QUGHK.js → tables-K5NAN2WK.js} +6 -6
- package/dist/templates/default/docs/yamchart-reference.md +42 -0
- package/dist/{test-UE5OWG3E.js → test-SRHVOXZB.js} +8 -7
- package/dist/{test-UE5OWG3E.js.map → test-SRHVOXZB.js.map} +1 -1
- package/package.json +3 -3
- package/dist/advisor-SC64RTZO.js.map +0 -1
- package/dist/chunk-NXQ6ZO3V.js.map +0 -1
- package/dist/chunk-RM6MNDVF.js.map +0 -1
- package/dist/chunk-S7YQXEKM.js.map +0 -1
- package/dist/chunk-UND73EOB.js.map +0 -1
- package/dist/dev-GWXHBBWB.js.map +0 -1
- package/dist/dist-MNXSMGV6.js +0 -790
- package/dist/dist-MNXSMGV6.js.map +0 -1
- /package/dist/{chunk-RMIDEBHD.js.map → chunk-S2CH4HUZ.js.map} +0 -0
- /package/dist/{chunk-H4L3FNLS.js.map → chunk-ZBCQNWVN.js.map} +0 -0
- /package/dist/{connection-utils-C4FQGBW6.js.map → connection-utils-FEUWER5I.js.map} +0 -0
- /package/dist/{describe-X75C2VDU.js.map → describe-MEP72B56.js.map} +0 -0
- /package/dist/{dist-JMLAZUL7.js.map → dist-5IFWLWND.js.map} +0 -0
- /package/dist/{dist-MX5K2ABB.js.map → dist-PPAD6KOM.js.map} +0 -0
- /package/dist/{source-resolver-R7WBIL7M.js.map → dist-XNCED7JW.js.map} +0 -0
- /package/dist/{query-QNRDS74I.js.map → query-2H3YOPI2.js.map} +0 -0
- /package/dist/{sample-SKLHBZBU.js.map → sample-ODUGGSFA.js.map} +0 -0
- /package/dist/{search-4KMETZVX.js.map → search-IPE4ISFB.js.map} +0 -0
- /package/dist/{sync-warehouse-UWRNUXE7.js.map → sync-warehouse-26L6JDSV.js.map} +0 -0
- /package/dist/{tables-V65QUGHK.js.map → tables-K5NAN2WK.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadEnvFile,
|
|
3
3
|
validateProject
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-S2CH4HUZ.js";
|
|
5
5
|
import {
|
|
6
6
|
box,
|
|
7
7
|
detail,
|
|
@@ -12,31 +12,14 @@ import {
|
|
|
12
12
|
spinner,
|
|
13
13
|
success
|
|
14
14
|
} from "./chunk-HJVVHYVN.js";
|
|
15
|
-
import {
|
|
16
|
-
ChartSchema,
|
|
17
|
-
ConnectionSchema,
|
|
18
|
-
DashboardSchema,
|
|
19
|
-
EventSchema,
|
|
20
|
-
EventsFileSchema,
|
|
21
|
-
ProjectSchema,
|
|
22
|
-
ScheduleSchema,
|
|
23
|
-
SemanticQuerySchema,
|
|
24
|
-
deepMerge,
|
|
25
|
-
resolveProjectConfig
|
|
26
|
-
} from "./chunk-RM6MNDVF.js";
|
|
27
15
|
import {
|
|
28
16
|
StreamingChatAgent,
|
|
29
17
|
filterCatalogEntries
|
|
30
18
|
} from "./chunk-ZMJPRNOA.js";
|
|
31
19
|
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
hashPassword,
|
|
36
|
-
hashToken,
|
|
37
|
-
parseTtl,
|
|
38
|
-
verifyPassword
|
|
39
|
-
} from "./chunk-DZVT6PHW.js";
|
|
20
|
+
createEmbedder,
|
|
21
|
+
createProviderForTask
|
|
22
|
+
} from "./chunk-FN6R2LAC.js";
|
|
40
23
|
import {
|
|
41
24
|
BigQueryConnector,
|
|
42
25
|
DuckDBConnector,
|
|
@@ -46,28 +29,56 @@ import {
|
|
|
46
29
|
ReconnectingConnector,
|
|
47
30
|
SQLiteConnector,
|
|
48
31
|
SnowflakeConnector,
|
|
49
|
-
|
|
32
|
+
buildSourceProfile,
|
|
50
33
|
createTemplateContext,
|
|
51
|
-
|
|
52
|
-
expandDatePreset,
|
|
53
|
-
expandRelativeDateRange,
|
|
54
|
-
formatPeriodLabel,
|
|
55
|
-
isCustomDateRange,
|
|
56
|
-
isDatePreset,
|
|
57
|
-
isRelativeDateRange,
|
|
34
|
+
isReadOnlySql,
|
|
58
35
|
parseModelMetadata,
|
|
59
36
|
renderTemplate,
|
|
60
37
|
resolveBigQueryAuth,
|
|
61
38
|
resolveMySQLAuth,
|
|
62
39
|
resolvePostgresAuth,
|
|
63
40
|
resolveSnowflakeAuth
|
|
64
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-QUIDZO5G.js";
|
|
65
42
|
import {
|
|
66
43
|
SemanticModelBuilder,
|
|
67
44
|
SemanticQueryCompiler,
|
|
68
45
|
SemanticValidationError,
|
|
46
|
+
buildSemanticLayer,
|
|
47
|
+
compileMetricQuery,
|
|
48
|
+
computePreviousPeriod,
|
|
49
|
+
expandCustomDateRange,
|
|
50
|
+
expandDatePreset,
|
|
51
|
+
expandRelativeDateRange,
|
|
52
|
+
formatPeriodLabel,
|
|
53
|
+
isCustomDateRange,
|
|
54
|
+
isDatePreset,
|
|
55
|
+
isRelativeDateRange,
|
|
56
|
+
loadSemanticDefinitions,
|
|
69
57
|
validateSemanticQuery
|
|
70
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-UFDQ3C7Q.js";
|
|
59
|
+
import {
|
|
60
|
+
ChartSchema,
|
|
61
|
+
ConnectionSchema,
|
|
62
|
+
DashboardSchema,
|
|
63
|
+
EventSchema,
|
|
64
|
+
EventsFileSchema,
|
|
65
|
+
NewBlindspotSchema,
|
|
66
|
+
ProjectSchema,
|
|
67
|
+
ScheduleSchema,
|
|
68
|
+
SemanticQuerySchema,
|
|
69
|
+
deepMerge,
|
|
70
|
+
resolveProjectConfig
|
|
71
|
+
} from "./chunk-ZIY22VO7.js";
|
|
72
|
+
import "./chunk-QJ5CPQJK.js";
|
|
73
|
+
import {
|
|
74
|
+
AuthDatabase,
|
|
75
|
+
generateOpaqueToken,
|
|
76
|
+
generateSessionToken,
|
|
77
|
+
hashPassword,
|
|
78
|
+
hashToken,
|
|
79
|
+
parseTtl,
|
|
80
|
+
verifyPassword
|
|
81
|
+
} from "./chunk-DZVT6PHW.js";
|
|
71
82
|
import {
|
|
72
83
|
__commonJS,
|
|
73
84
|
__toESM
|
|
@@ -2230,14 +2241,14 @@ var require_resolve = __commonJS({
|
|
|
2230
2241
|
"enum",
|
|
2231
2242
|
"const"
|
|
2232
2243
|
]);
|
|
2233
|
-
function inlineRef(schema,
|
|
2244
|
+
function inlineRef(schema, limit2 = true) {
|
|
2234
2245
|
if (typeof schema == "boolean")
|
|
2235
2246
|
return true;
|
|
2236
|
-
if (
|
|
2247
|
+
if (limit2 === true)
|
|
2237
2248
|
return !hasRef(schema);
|
|
2238
|
-
if (!
|
|
2249
|
+
if (!limit2)
|
|
2239
2250
|
return false;
|
|
2240
|
-
return countKeys(schema) <=
|
|
2251
|
+
return countKeys(schema) <= limit2;
|
|
2241
2252
|
}
|
|
2242
2253
|
exports.inlineRef = inlineRef;
|
|
2243
2254
|
var REF_KEYWORDS = /* @__PURE__ */ new Set([
|
|
@@ -7762,7 +7773,7 @@ var QueryService = class {
|
|
|
7762
7773
|
async executeRawSql(sql) {
|
|
7763
7774
|
return this.connector.execute(sql);
|
|
7764
7775
|
}
|
|
7765
|
-
async executeModel(modelName, params, userContext,
|
|
7776
|
+
async executeModel(modelName, params, userContext, limit2 = 1e3, filters) {
|
|
7766
7777
|
const model = this.models.get(modelName);
|
|
7767
7778
|
if (!model) {
|
|
7768
7779
|
throw new Error(`Unknown model: ${modelName}`);
|
|
@@ -7771,9 +7782,9 @@ var QueryService = class {
|
|
|
7771
7782
|
let sql = renderTemplate(model.sql, context);
|
|
7772
7783
|
if (filters && Object.keys(filters).length > 0) {
|
|
7773
7784
|
const clauses = Object.entries(filters).map(([field, value]) => `${field} = '${value.replace(/'/g, "''")}'`);
|
|
7774
|
-
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${
|
|
7785
|
+
sql = `SELECT * FROM (${sql}) _detail WHERE ${clauses.join(" AND ")} LIMIT ${limit2}`;
|
|
7775
7786
|
} else {
|
|
7776
|
-
sql = `SELECT * FROM (${sql}) _detail LIMIT ${
|
|
7787
|
+
sql = `SELECT * FROM (${sql}) _detail LIMIT ${limit2}`;
|
|
7777
7788
|
}
|
|
7778
7789
|
return this.connector.execute(sql);
|
|
7779
7790
|
}
|
|
@@ -8820,8 +8831,8 @@ async function semanticRoutes(fastify, options) {
|
|
|
8820
8831
|
});
|
|
8821
8832
|
}
|
|
8822
8833
|
const { writeFileSync, mkdirSync } = await import("fs");
|
|
8823
|
-
const { join:
|
|
8824
|
-
const fullPath =
|
|
8834
|
+
const { join: join12, dirname: dirname3 } = await import("path");
|
|
8835
|
+
const fullPath = join12(projectDir, filePath);
|
|
8825
8836
|
mkdirSync(dirname3(fullPath), { recursive: true });
|
|
8826
8837
|
writeFileSync(fullPath, sql, "utf-8");
|
|
8827
8838
|
return { saved: true, path: filePath };
|
|
@@ -9179,6 +9190,292 @@ var GitService = class {
|
|
|
9179
9190
|
}
|
|
9180
9191
|
};
|
|
9181
9192
|
|
|
9193
|
+
// ../../packages/knowledge/dist/memory-store.js
|
|
9194
|
+
import { randomUUID } from "crypto";
|
|
9195
|
+
|
|
9196
|
+
// ../../packages/knowledge/dist/postgres-store.js
|
|
9197
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
9198
|
+
import pg from "pg";
|
|
9199
|
+
|
|
9200
|
+
// ../../packages/knowledge/dist/migrations.js
|
|
9201
|
+
var MIGRATIONS = [
|
|
9202
|
+
`CREATE TABLE IF NOT EXISTS source_profiles (
|
|
9203
|
+
connection TEXT PRIMARY KEY,
|
|
9204
|
+
profiled_at TIMESTAMPTZ NOT NULL,
|
|
9205
|
+
profile JSONB NOT NULL
|
|
9206
|
+
);`,
|
|
9207
|
+
`CREATE TABLE IF NOT EXISTS hunches (
|
|
9208
|
+
id TEXT PRIMARY KEY,
|
|
9209
|
+
connection TEXT NOT NULL,
|
|
9210
|
+
question TEXT NOT NULL,
|
|
9211
|
+
sql TEXT NOT NULL,
|
|
9212
|
+
generated_sql TEXT NOT NULL,
|
|
9213
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
9214
|
+
tables_used JSONB NOT NULL DEFAULT '[]',
|
|
9215
|
+
rationale TEXT,
|
|
9216
|
+
confidence DOUBLE PRECISION,
|
|
9217
|
+
last_run_sample JSONB,
|
|
9218
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
9219
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
9220
|
+
);`,
|
|
9221
|
+
`CREATE INDEX IF NOT EXISTS hunches_conn_status ON hunches (connection, status);`,
|
|
9222
|
+
`CREATE TABLE IF NOT EXISTS blindspots (
|
|
9223
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, kind TEXT NOT NULL,
|
|
9224
|
+
title TEXT NOT NULL, detail TEXT NOT NULL, subject TEXT,
|
|
9225
|
+
suggested_model JSONB, confidence DOUBLE PRECISION,
|
|
9226
|
+
status TEXT NOT NULL DEFAULT 'open', answer TEXT,
|
|
9227
|
+
created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
|
|
9228
|
+
);`,
|
|
9229
|
+
`CREATE INDEX IF NOT EXISTS blindspots_conn_status ON blindspots (connection, status);`,
|
|
9230
|
+
`CREATE TABLE IF NOT EXISTS knowledge_docs (
|
|
9231
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL,
|
|
9232
|
+
source TEXT NOT NULL DEFAULT 'ui', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
|
|
9233
|
+
);`,
|
|
9234
|
+
`CREATE INDEX IF NOT EXISTS knowledge_docs_conn ON knowledge_docs (connection);`,
|
|
9235
|
+
`CREATE TABLE IF NOT EXISTS knowledge_embeddings (
|
|
9236
|
+
id TEXT PRIMARY KEY, connection TEXT NOT NULL, source_kind TEXT NOT NULL, source_id TEXT NOT NULL,
|
|
9237
|
+
text TEXT NOT NULL, vector JSONB NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
9238
|
+
UNIQUE (source_kind, source_id)
|
|
9239
|
+
);`,
|
|
9240
|
+
`CREATE INDEX IF NOT EXISTS knowledge_embeddings_conn ON knowledge_embeddings (connection);`
|
|
9241
|
+
];
|
|
9242
|
+
|
|
9243
|
+
// ../../packages/knowledge/dist/postgres-store.js
|
|
9244
|
+
function rowToBlindspot(r) {
|
|
9245
|
+
return {
|
|
9246
|
+
id: r.id,
|
|
9247
|
+
connection: r.connection,
|
|
9248
|
+
kind: r.kind,
|
|
9249
|
+
title: r.title,
|
|
9250
|
+
detail: r.detail,
|
|
9251
|
+
subject: r.subject ?? void 0,
|
|
9252
|
+
suggestedModel: r.suggested_model ?? void 0,
|
|
9253
|
+
confidence: r.confidence ?? void 0,
|
|
9254
|
+
status: r.status,
|
|
9255
|
+
answer: r.answer ?? void 0,
|
|
9256
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9257
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9258
|
+
};
|
|
9259
|
+
}
|
|
9260
|
+
function rowToKnowledgeDoc(r) {
|
|
9261
|
+
return {
|
|
9262
|
+
id: r.id,
|
|
9263
|
+
connection: r.connection,
|
|
9264
|
+
title: r.title,
|
|
9265
|
+
content: r.content,
|
|
9266
|
+
source: r.source,
|
|
9267
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9268
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9269
|
+
};
|
|
9270
|
+
}
|
|
9271
|
+
function rowToHunch(r) {
|
|
9272
|
+
return {
|
|
9273
|
+
id: r.id,
|
|
9274
|
+
connection: r.connection,
|
|
9275
|
+
question: r.question,
|
|
9276
|
+
sql: r.sql,
|
|
9277
|
+
generatedSql: r.generated_sql,
|
|
9278
|
+
status: r.status,
|
|
9279
|
+
tablesUsed: r.tables_used ?? [],
|
|
9280
|
+
rationale: r.rationale ?? void 0,
|
|
9281
|
+
confidence: r.confidence ?? void 0,
|
|
9282
|
+
lastRunSample: r.last_run_sample ?? void 0,
|
|
9283
|
+
createdAt: new Date(r.created_at).toISOString(),
|
|
9284
|
+
updatedAt: new Date(r.updated_at).toISOString()
|
|
9285
|
+
};
|
|
9286
|
+
}
|
|
9287
|
+
var PostgresKnowledgeStore = class {
|
|
9288
|
+
pool;
|
|
9289
|
+
constructor(connectionString) {
|
|
9290
|
+
this.pool = new pg.Pool({ connectionString });
|
|
9291
|
+
}
|
|
9292
|
+
async init() {
|
|
9293
|
+
for (const m of MIGRATIONS)
|
|
9294
|
+
await this.pool.query(m);
|
|
9295
|
+
}
|
|
9296
|
+
async saveProfile(connection, profile) {
|
|
9297
|
+
await this.pool.query(`INSERT INTO source_profiles (connection, profiled_at, profile) VALUES ($1,$2,$3)
|
|
9298
|
+
ON CONFLICT (connection) DO UPDATE SET profiled_at = EXCLUDED.profiled_at, profile = EXCLUDED.profile`, [connection, profile.profiledAt, JSON.stringify(profile)]);
|
|
9299
|
+
}
|
|
9300
|
+
async getProfile(connection) {
|
|
9301
|
+
const { rows } = await this.pool.query(`SELECT profile FROM source_profiles WHERE connection = $1`, [connection]);
|
|
9302
|
+
return rows[0]?.profile;
|
|
9303
|
+
}
|
|
9304
|
+
async createHunches(items) {
|
|
9305
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9306
|
+
const out = [];
|
|
9307
|
+
for (const it of items) {
|
|
9308
|
+
const id = randomUUID2();
|
|
9309
|
+
const { rows } = await this.pool.query(`INSERT INTO hunches (id, connection, question, sql, generated_sql, status, tables_used, rationale, confidence, created_at, updated_at)
|
|
9310
|
+
VALUES ($1,$2,$3,$4,$5,'pending',$6,$7,$8,$9,$9) RETURNING *`, [id, it.connection, it.question, it.sql, it.generatedSql, JSON.stringify(it.tablesUsed ?? []), it.rationale ?? null, it.confidence ?? null, now]);
|
|
9311
|
+
out.push(rowToHunch(rows[0]));
|
|
9312
|
+
}
|
|
9313
|
+
return out;
|
|
9314
|
+
}
|
|
9315
|
+
async listHunches(filter) {
|
|
9316
|
+
const clauses = [];
|
|
9317
|
+
const params = [];
|
|
9318
|
+
if (filter.connection) {
|
|
9319
|
+
params.push(filter.connection);
|
|
9320
|
+
clauses.push(`connection = $${params.length}`);
|
|
9321
|
+
}
|
|
9322
|
+
if (filter.status) {
|
|
9323
|
+
params.push(filter.status);
|
|
9324
|
+
clauses.push(`status = $${params.length}`);
|
|
9325
|
+
}
|
|
9326
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9327
|
+
const { rows } = await this.pool.query(`SELECT * FROM hunches ${where} ORDER BY created_at DESC`, params);
|
|
9328
|
+
return rows.map(rowToHunch);
|
|
9329
|
+
}
|
|
9330
|
+
async getHunch(id) {
|
|
9331
|
+
const { rows } = await this.pool.query(`SELECT * FROM hunches WHERE id = $1`, [id]);
|
|
9332
|
+
return rows[0] ? rowToHunch(rows[0]) : void 0;
|
|
9333
|
+
}
|
|
9334
|
+
async updateHunch(id, patch) {
|
|
9335
|
+
const sets = [];
|
|
9336
|
+
const params = [];
|
|
9337
|
+
if (patch.sql !== void 0) {
|
|
9338
|
+
params.push(patch.sql);
|
|
9339
|
+
sets.push(`sql = $${params.length}`);
|
|
9340
|
+
}
|
|
9341
|
+
if (patch.status !== void 0) {
|
|
9342
|
+
params.push(patch.status);
|
|
9343
|
+
sets.push(`status = $${params.length}`);
|
|
9344
|
+
}
|
|
9345
|
+
if (patch.lastRunSample !== void 0) {
|
|
9346
|
+
params.push(JSON.stringify(patch.lastRunSample));
|
|
9347
|
+
sets.push(`last_run_sample = $${params.length}`);
|
|
9348
|
+
}
|
|
9349
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9350
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9351
|
+
params.push(id);
|
|
9352
|
+
const { rows } = await this.pool.query(`UPDATE hunches SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9353
|
+
if (!rows[0])
|
|
9354
|
+
throw new Error(`Hunch "${id}" not found.`);
|
|
9355
|
+
return rowToHunch(rows[0]);
|
|
9356
|
+
}
|
|
9357
|
+
async createBlindspots(items) {
|
|
9358
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9359
|
+
const out = [];
|
|
9360
|
+
for (const it of items) {
|
|
9361
|
+
const id = randomUUID2();
|
|
9362
|
+
const { rows } = await this.pool.query(`INSERT INTO blindspots (id, connection, kind, title, detail, subject, suggested_model, confidence, status, created_at, updated_at)
|
|
9363
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'open',$9,$9) RETURNING *`, [id, it.connection, it.kind, it.title, it.detail, it.subject ?? null, JSON.stringify(it.suggestedModel ?? null), it.confidence ?? null, now]);
|
|
9364
|
+
out.push(rowToBlindspot(rows[0]));
|
|
9365
|
+
}
|
|
9366
|
+
return out;
|
|
9367
|
+
}
|
|
9368
|
+
async listBlindspots(filter) {
|
|
9369
|
+
const clauses = [];
|
|
9370
|
+
const params = [];
|
|
9371
|
+
if (filter.connection) {
|
|
9372
|
+
params.push(filter.connection);
|
|
9373
|
+
clauses.push(`connection = $${params.length}`);
|
|
9374
|
+
}
|
|
9375
|
+
if (filter.status) {
|
|
9376
|
+
params.push(filter.status);
|
|
9377
|
+
clauses.push(`status = $${params.length}`);
|
|
9378
|
+
}
|
|
9379
|
+
if (filter.kind) {
|
|
9380
|
+
params.push(filter.kind);
|
|
9381
|
+
clauses.push(`kind = $${params.length}`);
|
|
9382
|
+
}
|
|
9383
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9384
|
+
const { rows } = await this.pool.query(`SELECT * FROM blindspots ${where} ORDER BY created_at DESC`, params);
|
|
9385
|
+
return rows.map(rowToBlindspot);
|
|
9386
|
+
}
|
|
9387
|
+
async getBlindspot(id) {
|
|
9388
|
+
const { rows } = await this.pool.query(`SELECT * FROM blindspots WHERE id = $1`, [id]);
|
|
9389
|
+
return rows[0] ? rowToBlindspot(rows[0]) : void 0;
|
|
9390
|
+
}
|
|
9391
|
+
async updateBlindspot(id, patch) {
|
|
9392
|
+
const sets = [];
|
|
9393
|
+
const params = [];
|
|
9394
|
+
if (patch.status !== void 0) {
|
|
9395
|
+
params.push(patch.status);
|
|
9396
|
+
sets.push(`status = $${params.length}`);
|
|
9397
|
+
}
|
|
9398
|
+
if (patch.answer !== void 0) {
|
|
9399
|
+
params.push(patch.answer);
|
|
9400
|
+
sets.push(`answer = $${params.length}`);
|
|
9401
|
+
}
|
|
9402
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9403
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9404
|
+
params.push(id);
|
|
9405
|
+
const { rows } = await this.pool.query(`UPDATE blindspots SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9406
|
+
if (!rows[0])
|
|
9407
|
+
throw new Error(`Blindspot "${id}" not found.`);
|
|
9408
|
+
return rowToBlindspot(rows[0]);
|
|
9409
|
+
}
|
|
9410
|
+
async deleteOpenBlindspots(connection) {
|
|
9411
|
+
await this.pool.query(`DELETE FROM blindspots WHERE connection = $1 AND status = 'open'`, [connection]);
|
|
9412
|
+
}
|
|
9413
|
+
async createKnowledgeDoc(doc) {
|
|
9414
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9415
|
+
const id = randomUUID2();
|
|
9416
|
+
const { rows } = await this.pool.query(`INSERT INTO knowledge_docs (id, connection, title, content, source, created_at, updated_at)
|
|
9417
|
+
VALUES ($1,$2,$3,$4,$5,$6,$6) RETURNING *`, [id, doc.connection, doc.title, doc.content, doc.source, now]);
|
|
9418
|
+
return [rowToKnowledgeDoc(rows[0])];
|
|
9419
|
+
}
|
|
9420
|
+
async listKnowledgeDocs(filter) {
|
|
9421
|
+
const clauses = [];
|
|
9422
|
+
const params = [];
|
|
9423
|
+
if (filter.connection) {
|
|
9424
|
+
params.push(filter.connection);
|
|
9425
|
+
clauses.push(`connection = $${params.length}`);
|
|
9426
|
+
}
|
|
9427
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9428
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_docs ${where} ORDER BY created_at DESC`, params);
|
|
9429
|
+
return rows.map(rowToKnowledgeDoc);
|
|
9430
|
+
}
|
|
9431
|
+
async updateKnowledgeDoc(id, patch) {
|
|
9432
|
+
const sets = [];
|
|
9433
|
+
const params = [];
|
|
9434
|
+
if (patch.title !== void 0) {
|
|
9435
|
+
params.push(patch.title);
|
|
9436
|
+
sets.push(`title = $${params.length}`);
|
|
9437
|
+
}
|
|
9438
|
+
if (patch.content !== void 0) {
|
|
9439
|
+
params.push(patch.content);
|
|
9440
|
+
sets.push(`content = $${params.length}`);
|
|
9441
|
+
}
|
|
9442
|
+
params.push((/* @__PURE__ */ new Date()).toISOString());
|
|
9443
|
+
sets.push(`updated_at = $${params.length}`);
|
|
9444
|
+
params.push(id);
|
|
9445
|
+
const { rows } = await this.pool.query(`UPDATE knowledge_docs SET ${sets.join(", ")} WHERE id = $${params.length} RETURNING *`, params);
|
|
9446
|
+
if (!rows[0])
|
|
9447
|
+
throw new Error(`KnowledgeDoc "${id}" not found.`);
|
|
9448
|
+
return rowToKnowledgeDoc(rows[0]);
|
|
9449
|
+
}
|
|
9450
|
+
async deleteKnowledgeDoc(id) {
|
|
9451
|
+
await this.pool.query(`DELETE FROM knowledge_docs WHERE id = $1`, [id]);
|
|
9452
|
+
}
|
|
9453
|
+
async upsertEmbedding(e) {
|
|
9454
|
+
const id = `${e.source_kind}:${e.source_id}`;
|
|
9455
|
+
await this.pool.query(`INSERT INTO knowledge_embeddings (id, connection, source_kind, source_id, text, vector, updated_at)
|
|
9456
|
+
VALUES ($1,$2,$3,$4,$5,$6,now())
|
|
9457
|
+
ON CONFLICT (source_kind, source_id) DO UPDATE SET text=EXCLUDED.text, vector=EXCLUDED.vector, updated_at=now()`, [id, e.connection, e.source_kind, e.source_id, e.text, JSON.stringify(e.vector)]);
|
|
9458
|
+
}
|
|
9459
|
+
async listEmbeddings(filter) {
|
|
9460
|
+
const clauses = [];
|
|
9461
|
+
const params = [];
|
|
9462
|
+
if (filter.connection) {
|
|
9463
|
+
params.push(filter.connection);
|
|
9464
|
+
clauses.push(`connection = $${params.length}`);
|
|
9465
|
+
}
|
|
9466
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
9467
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings ${where}`, params);
|
|
9468
|
+
return rows.map((r) => ({ id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector }));
|
|
9469
|
+
}
|
|
9470
|
+
async getEmbedding(source_kind, source_id) {
|
|
9471
|
+
const { rows } = await this.pool.query(`SELECT * FROM knowledge_embeddings WHERE source_kind = $1 AND source_id = $2`, [source_kind, source_id]);
|
|
9472
|
+
if (!rows[0])
|
|
9473
|
+
return void 0;
|
|
9474
|
+
const r = rows[0];
|
|
9475
|
+
return { id: r.id, connection: r.connection, source_kind: r.source_kind, source_id: r.source_id, text: r.text, vector: r.vector };
|
|
9476
|
+
}
|
|
9477
|
+
};
|
|
9478
|
+
|
|
9182
9479
|
// ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
|
|
9183
9480
|
function __rest(s, e) {
|
|
9184
9481
|
var t = {};
|
|
@@ -11868,8 +12165,8 @@ var RealtimeChannel = class _RealtimeChannel {
|
|
|
11868
12165
|
_trigger(type, payload, ref) {
|
|
11869
12166
|
var _a, _b;
|
|
11870
12167
|
const typeLower = type.toLocaleLowerCase();
|
|
11871
|
-
const { close, error: error2, leave, join:
|
|
11872
|
-
const events = [close, error2, leave,
|
|
12168
|
+
const { close, error: error2, leave, join: join12 } = CHANNEL_EVENTS;
|
|
12169
|
+
const events = [close, error2, leave, join12];
|
|
11873
12170
|
if (ref && events.indexOf(typeLower) >= 0 && ref !== this._joinRef()) {
|
|
11874
12171
|
return;
|
|
11875
12172
|
}
|
|
@@ -21429,7 +21726,7 @@ async function chatRoutes(fastify, options) {
|
|
|
21429
21726
|
systemParts.push("Output ONLY the new text to insert. Do NOT repeat the existing content.");
|
|
21430
21727
|
}
|
|
21431
21728
|
const systemPrompt = systemParts.join("\n");
|
|
21432
|
-
const { AnthropicProvider } = await import("./dist-
|
|
21729
|
+
const { AnthropicProvider } = await import("./dist-5IFWLWND.js");
|
|
21433
21730
|
const provider = new AnthropicProvider(apiKey);
|
|
21434
21731
|
const response = await provider.chat({
|
|
21435
21732
|
system: systemPrompt,
|
|
@@ -28527,8 +28824,8 @@ async function runSql(deps, args) {
|
|
|
28527
28824
|
if (DESTRUCTIVE.test(sql) || /^\s*WITH\b/i.test(sql) && WRITABLE_CTE.test(sql)) {
|
|
28528
28825
|
return "Error: Destructive SQL statements are not allowed through the MCP.";
|
|
28529
28826
|
}
|
|
28530
|
-
const
|
|
28531
|
-
const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${
|
|
28827
|
+
const limit2 = args.limit ?? DEFAULT_LIMIT;
|
|
28828
|
+
const limited = /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT ${limit2}`;
|
|
28532
28829
|
const result = await deps.queryService.executeRawSql(limited);
|
|
28533
28830
|
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
28534
28831
|
}
|
|
@@ -28584,8 +28881,9 @@ async function listTables(deps) {
|
|
|
28584
28881
|
})));
|
|
28585
28882
|
}
|
|
28586
28883
|
async function describeTable(deps, args) {
|
|
28587
|
-
const
|
|
28588
|
-
const
|
|
28884
|
+
const normalizedTable = args.schema != null && !args.table.includes(".") ? `${args.schema}.${args.table}` : args.table;
|
|
28885
|
+
const parts = normalizedTable.split(".");
|
|
28886
|
+
const tableName = parts[parts.length - 1] ?? normalizedTable;
|
|
28589
28887
|
const schema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
28590
28888
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
28591
28889
|
const match = tables.find((t) => t.name === tableName && (schema === void 0 || t.schema === schema));
|
|
@@ -28603,14 +28901,14 @@ async function sampleTable(deps, args) {
|
|
|
28603
28901
|
if (!TABLE_IDENT.test(args.table)) {
|
|
28604
28902
|
return `Error: Invalid table identifier "${args.table}".`;
|
|
28605
28903
|
}
|
|
28606
|
-
const
|
|
28904
|
+
const limit2 = Math.min(Math.max(args.limit ?? 5, 1), 100);
|
|
28607
28905
|
const parts = args.table.split(".");
|
|
28608
28906
|
const tableName = parts[parts.length - 1] ?? args.table;
|
|
28609
28907
|
const providedSchema = parts.length > 1 ? parts.slice(0, -1).join(".") : void 0;
|
|
28610
28908
|
const tables = await readCatalogTables(deps.projectDir, includeFilter(deps));
|
|
28611
28909
|
const match = tables.find((t) => t.name === tableName && (providedSchema === void 0 || t.schema === providedSchema));
|
|
28612
28910
|
const from = providedSchema === void 0 && match?.schema ? `${match.schema}.${args.table}` : args.table;
|
|
28613
|
-
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${
|
|
28911
|
+
const result = await deps.queryService.executeRawSql(`SELECT * FROM ${from} LIMIT ${limit2}`);
|
|
28614
28912
|
return JSON.stringify({ columns: result.columns, rows: result.rows });
|
|
28615
28913
|
}
|
|
28616
28914
|
|
|
@@ -28691,11 +28989,40 @@ function renderChartToPng(option, opts = {}) {
|
|
|
28691
28989
|
return Buffer.from(resvg.render().asPng());
|
|
28692
28990
|
}
|
|
28693
28991
|
|
|
28992
|
+
// ../../packages/mcp-core/dist/chart-errors.js
|
|
28993
|
+
function flattenIssues(issues) {
|
|
28994
|
+
const flat = [];
|
|
28995
|
+
for (const issue of issues) {
|
|
28996
|
+
if (issue.code === "invalid_union" && issue.unionErrors?.length) {
|
|
28997
|
+
const typeMatchMember = issue.unionErrors.find((e) => !e.issues.some((i) => i.path.length >= 2 && i.path[i.path.length - 1] === "type" && i.code === "invalid_literal"));
|
|
28998
|
+
const best = typeMatchMember ?? issue.unionErrors.reduce((a, b) => a.issues.length <= b.issues.length ? a : b);
|
|
28999
|
+
flat.push(...flattenIssues(best.issues));
|
|
29000
|
+
} else {
|
|
29001
|
+
const path = issue.path.length ? issue.path.join(".") : "(root)";
|
|
29002
|
+
let detail2 = issue.message;
|
|
29003
|
+
if (/expected object, received string/i.test(issue.message)) {
|
|
29004
|
+
detail2 = `expected an object like { field: "<column>", type: "temporal|quantitative|ordinal|nominal" }, received a string`;
|
|
29005
|
+
}
|
|
29006
|
+
flat.push({ path, message: detail2 });
|
|
29007
|
+
}
|
|
29008
|
+
}
|
|
29009
|
+
return flat;
|
|
29010
|
+
}
|
|
29011
|
+
function formatChartErrors(error2) {
|
|
29012
|
+
const issues = flattenIssues(error2.issues);
|
|
29013
|
+
const lines = issues.map((i) => ` \u2022 ${i.path}: ${i.message}`);
|
|
29014
|
+
return [
|
|
29015
|
+
"Invalid chart spec:",
|
|
29016
|
+
...lines,
|
|
29017
|
+
"Tip: call get_chart_schema (optionally with { type }) for the exact shape and a working example."
|
|
29018
|
+
].join("\n");
|
|
29019
|
+
}
|
|
29020
|
+
|
|
28694
29021
|
// ../../packages/mcp-core/dist/tools/visualize.js
|
|
28695
29022
|
async function visualize(_deps, args) {
|
|
28696
29023
|
const parsed = ChartSchema.safeParse(args.spec);
|
|
28697
29024
|
if (!parsed.success) {
|
|
28698
|
-
return { error:
|
|
29025
|
+
return { error: formatChartErrors(parsed.error) };
|
|
28699
29026
|
}
|
|
28700
29027
|
try {
|
|
28701
29028
|
const option = buildEChartsOption(parsed.data, args.data ?? []);
|
|
@@ -28722,7 +29049,7 @@ async function saveChart(deps, args) {
|
|
|
28722
29049
|
}
|
|
28723
29050
|
const parsed = ChartSchema.safeParse(args.spec);
|
|
28724
29051
|
if (!parsed.success) {
|
|
28725
|
-
return `Error:
|
|
29052
|
+
return `Error: ${formatChartErrors(parsed.error)}`;
|
|
28726
29053
|
}
|
|
28727
29054
|
const name = parsed.data.name;
|
|
28728
29055
|
if (!SAFE_NAME.test(name)) {
|
|
@@ -28738,6 +29065,450 @@ async function saveChart(deps, args) {
|
|
|
28738
29065
|
return `Chart "${name}" saved to charts/${name}.yaml`;
|
|
28739
29066
|
}
|
|
28740
29067
|
|
|
29068
|
+
// ../../packages/mcp-core/dist/suggested-chart.js
|
|
29069
|
+
function suggestChart(input) {
|
|
29070
|
+
const { name, title, metrics, timeDimension, dimensions } = input;
|
|
29071
|
+
const primary = metrics[0] ?? { name: "value", format: void 0 };
|
|
29072
|
+
if (dimensions.length === 0) {
|
|
29073
|
+
return {
|
|
29074
|
+
name,
|
|
29075
|
+
title,
|
|
29076
|
+
chart: {
|
|
29077
|
+
type: "kpi",
|
|
29078
|
+
value: { field: primary.name },
|
|
29079
|
+
// KpiFormatSchema requires `type` — always include it.
|
|
29080
|
+
format: primary.format?.includes("$") ? { type: "currency", currency: "USD", decimals: 0 } : { type: "number" }
|
|
29081
|
+
}
|
|
29082
|
+
};
|
|
29083
|
+
}
|
|
29084
|
+
const groupField = dimensions[0] ?? "dimension";
|
|
29085
|
+
const isTime = timeDimension && dimensions.includes(timeDimension);
|
|
29086
|
+
return {
|
|
29087
|
+
name,
|
|
29088
|
+
title,
|
|
29089
|
+
chart: {
|
|
29090
|
+
type: "bar",
|
|
29091
|
+
x: { field: groupField, type: isTime ? "temporal" : "nominal" },
|
|
29092
|
+
y: {
|
|
29093
|
+
field: primary.name,
|
|
29094
|
+
type: "quantitative",
|
|
29095
|
+
label: primary.name,
|
|
29096
|
+
...primary.format ? { format: primary.format } : {}
|
|
29097
|
+
}
|
|
29098
|
+
}
|
|
29099
|
+
};
|
|
29100
|
+
}
|
|
29101
|
+
|
|
29102
|
+
// ../../packages/mcp-core/dist/tools/semantic.js
|
|
29103
|
+
function listMetrics(deps) {
|
|
29104
|
+
return JSON.stringify(deps.semanticLayer.listMetrics(), null, 2);
|
|
29105
|
+
}
|
|
29106
|
+
function getMetric(deps, args) {
|
|
29107
|
+
const resolved = deps.semanticLayer.resolveMetric(args.name);
|
|
29108
|
+
if (!resolved) {
|
|
29109
|
+
return JSON.stringify({ error: `Unknown metric "${args.name}". Call list_metrics to see governed metrics.` });
|
|
29110
|
+
}
|
|
29111
|
+
const { metric, entity } = resolved;
|
|
29112
|
+
return JSON.stringify({
|
|
29113
|
+
name: metric.name,
|
|
29114
|
+
label: metric.label,
|
|
29115
|
+
description: metric.description,
|
|
29116
|
+
entity: entity.name,
|
|
29117
|
+
source: entity.sourceRef,
|
|
29118
|
+
agg: metric.agg,
|
|
29119
|
+
field: metric.field,
|
|
29120
|
+
sql: metric.sql,
|
|
29121
|
+
filter: metric.filter,
|
|
29122
|
+
filter_sql: metric.filter ? entity.filters[metric.filter]?.sql : void 0,
|
|
29123
|
+
synonyms: metric.synonyms ?? [],
|
|
29124
|
+
format: metric.format,
|
|
29125
|
+
dimensions: entity.dimensions,
|
|
29126
|
+
time_dimension: entity.timeDimension
|
|
29127
|
+
}, null, 2);
|
|
29128
|
+
}
|
|
29129
|
+
async function queryMetric(deps, args) {
|
|
29130
|
+
const resolved = args.metrics.map((m) => {
|
|
29131
|
+
const r = deps.semanticLayer.resolveMetric(m);
|
|
29132
|
+
if (!r)
|
|
29133
|
+
throw new Error(`Unknown metric "${m}". Call list_metrics.`);
|
|
29134
|
+
return r;
|
|
29135
|
+
});
|
|
29136
|
+
if (resolved.length === 0) {
|
|
29137
|
+
return JSON.stringify({ error: "At least one metric is required." });
|
|
29138
|
+
}
|
|
29139
|
+
const entity = resolved[0].entity;
|
|
29140
|
+
if (resolved.some((r) => r.entity.name !== entity.name)) {
|
|
29141
|
+
return JSON.stringify({
|
|
29142
|
+
error: `Metrics span multiple entities (${[...new Set(resolved.map((r) => r.entity.name))].join(", ")}). Query one entity at a time in Phase 1.`
|
|
29143
|
+
});
|
|
29144
|
+
}
|
|
29145
|
+
const canonicalMetrics = resolved.map((r) => r.metric.name);
|
|
29146
|
+
const { sql, metrics } = compileMetricQuery(entity, { ...args, metrics: canonicalMetrics }, deps.connectionType);
|
|
29147
|
+
const result = await deps.queryService.executeRawSql(sql);
|
|
29148
|
+
const explicitDimensions = args.dimensions ?? [];
|
|
29149
|
+
const effectiveDimensions = args.grain && entity.timeDimension && !explicitDimensions.includes(entity.timeDimension) ? [entity.timeDimension, ...explicitDimensions] : explicitDimensions;
|
|
29150
|
+
const suggested_chart = suggestChart({
|
|
29151
|
+
name: `metric_${canonicalMetrics.join("_")}`.slice(0, 60),
|
|
29152
|
+
title: metrics.map((m) => m.label ?? m.name).join(", "),
|
|
29153
|
+
metrics: metrics.map((m) => ({ name: m.name, format: m.format })),
|
|
29154
|
+
timeDimension: entity.timeDimension,
|
|
29155
|
+
dimensions: effectiveDimensions
|
|
29156
|
+
});
|
|
29157
|
+
return JSON.stringify({ columns: result.columns, rows: result.rows, sql, suggested_chart }, null, 2);
|
|
29158
|
+
}
|
|
29159
|
+
|
|
29160
|
+
// ../../packages/mcp-core/dist/tools/chart-dx.js
|
|
29161
|
+
var EXAMPLES = {
|
|
29162
|
+
bar: {
|
|
29163
|
+
name: "revenue_by_month",
|
|
29164
|
+
title: "Revenue by Month",
|
|
29165
|
+
source: { model: "monthly_revenue" },
|
|
29166
|
+
chart: {
|
|
29167
|
+
type: "bar",
|
|
29168
|
+
x: { field: "month", type: "temporal" },
|
|
29169
|
+
y: { field: "invoiced_revenue", type: "quantitative", format: "$,.0f" }
|
|
29170
|
+
}
|
|
29171
|
+
},
|
|
29172
|
+
line: {
|
|
29173
|
+
name: "revenue_trend",
|
|
29174
|
+
title: "Revenue Trend",
|
|
29175
|
+
source: { model: "monthly_revenue" },
|
|
29176
|
+
chart: {
|
|
29177
|
+
type: "line",
|
|
29178
|
+
x: { field: "month", type: "temporal" },
|
|
29179
|
+
y: { field: "invoiced_revenue", type: "quantitative" }
|
|
29180
|
+
}
|
|
29181
|
+
},
|
|
29182
|
+
area: {
|
|
29183
|
+
name: "revenue_area",
|
|
29184
|
+
title: "Revenue Area",
|
|
29185
|
+
source: { model: "monthly_revenue" },
|
|
29186
|
+
chart: {
|
|
29187
|
+
type: "area",
|
|
29188
|
+
x: { field: "month", type: "temporal" },
|
|
29189
|
+
y: { field: "invoiced_revenue", type: "quantitative" }
|
|
29190
|
+
}
|
|
29191
|
+
},
|
|
29192
|
+
kpi: {
|
|
29193
|
+
name: "total_revenue",
|
|
29194
|
+
title: "Total Revenue",
|
|
29195
|
+
source: { model: "total_revenue" },
|
|
29196
|
+
chart: {
|
|
29197
|
+
type: "kpi",
|
|
29198
|
+
value: { field: "total_revenue" },
|
|
29199
|
+
format: { type: "currency", currency: "USD", decimals: 0 }
|
|
29200
|
+
}
|
|
29201
|
+
},
|
|
29202
|
+
pie: {
|
|
29203
|
+
name: "revenue_by_category",
|
|
29204
|
+
title: "Revenue by Category",
|
|
29205
|
+
source: { model: "category_revenue" },
|
|
29206
|
+
chart: {
|
|
29207
|
+
type: "pie",
|
|
29208
|
+
x: { field: "category", type: "nominal" },
|
|
29209
|
+
y: { field: "revenue", type: "quantitative" }
|
|
29210
|
+
}
|
|
29211
|
+
},
|
|
29212
|
+
scatter: {
|
|
29213
|
+
name: "jobs_scatter",
|
|
29214
|
+
title: "Jobs Scatter",
|
|
29215
|
+
source: { model: "jobs" },
|
|
29216
|
+
chart: {
|
|
29217
|
+
type: "scatter",
|
|
29218
|
+
x: { field: "cost", type: "quantitative" },
|
|
29219
|
+
y: { field: "revenue", type: "quantitative" }
|
|
29220
|
+
}
|
|
29221
|
+
},
|
|
29222
|
+
table: {
|
|
29223
|
+
name: "jobs_table",
|
|
29224
|
+
title: "Jobs Table",
|
|
29225
|
+
source: { model: "jobs" },
|
|
29226
|
+
chart: {
|
|
29227
|
+
type: "table",
|
|
29228
|
+
columns: [
|
|
29229
|
+
{ field: "job_id", label: "Job ID" },
|
|
29230
|
+
{ field: "revenue", label: "Revenue", format: "$,.0f" }
|
|
29231
|
+
]
|
|
29232
|
+
}
|
|
29233
|
+
}
|
|
29234
|
+
};
|
|
29235
|
+
function getChartSchema(args) {
|
|
29236
|
+
const type = args.type != null && EXAMPLES[args.type] != null ? args.type : "bar";
|
|
29237
|
+
return JSON.stringify({
|
|
29238
|
+
note: "Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }. source is { model } or { sql }.",
|
|
29239
|
+
available_examples: Object.keys(EXAMPLES),
|
|
29240
|
+
example: EXAMPLES[type]
|
|
29241
|
+
}, null, 2);
|
|
29242
|
+
}
|
|
29243
|
+
function validateChart(args) {
|
|
29244
|
+
const parsed = ChartSchema.safeParse(args.spec);
|
|
29245
|
+
if (parsed.success)
|
|
29246
|
+
return JSON.stringify({ valid: true });
|
|
29247
|
+
return JSON.stringify({ valid: false, errors: formatChartErrors(parsed.error) });
|
|
29248
|
+
}
|
|
29249
|
+
function getChartSpec(deps, args) {
|
|
29250
|
+
const spec = deps.configLoader.getChartByName(args.name);
|
|
29251
|
+
if (spec == null) {
|
|
29252
|
+
return JSON.stringify({ error: `Chart "${args.name}" not found. Call list_charts to see available charts.` });
|
|
29253
|
+
}
|
|
29254
|
+
return JSON.stringify(spec, null, 2);
|
|
29255
|
+
}
|
|
29256
|
+
|
|
29257
|
+
// ../../packages/learning/dist/prompt.js
|
|
29258
|
+
function buildGenerationPrompt(profile, metricNames, count, contextBlock = "") {
|
|
29259
|
+
const tables = profile.tables.map((t) => {
|
|
29260
|
+
const cols = t.columns.map((c) => {
|
|
29261
|
+
const top = c.topValues?.length ? ` top:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
|
|
29262
|
+
const dist = c.distinctCount != null ? ` distinct:${c.distinctCount}` : "";
|
|
29263
|
+
return ` - ${c.name} (${c.type})${dist}${top}`;
|
|
29264
|
+
}).join("\n");
|
|
29265
|
+
return ` ${t.table} (${t.rowCount ?? "?"} rows):
|
|
29266
|
+
${cols}`;
|
|
29267
|
+
}).join("\n");
|
|
29268
|
+
return [
|
|
29269
|
+
`You are profiling a data warehouse to propose ${count} useful business questions a user would ask, each with correct read-only SQL.`,
|
|
29270
|
+
contextBlock ? `Known business context:
|
|
29271
|
+
${contextBlock}` : "",
|
|
29272
|
+
metricNames.length ? `Prefer these governed metrics when relevant: ${metricNames.join(", ")}.` : "",
|
|
29273
|
+
`Use the ACTUAL column values shown (e.g. status values) \u2014 do not invent values.`,
|
|
29274
|
+
`Tables and profiled columns:`,
|
|
29275
|
+
tables,
|
|
29276
|
+
`Respond with ONLY JSON: {"hunches":[{"question":string,"sql":string,"tablesUsed":string[],"confidence":number,"rationale":string}]}. SQL must be a single read-only SELECT.`
|
|
29277
|
+
].filter(Boolean).join("\n");
|
|
29278
|
+
}
|
|
29279
|
+
|
|
29280
|
+
// ../../packages/learning/dist/json.js
|
|
29281
|
+
function extractJson(text2) {
|
|
29282
|
+
const start = text2.indexOf("{");
|
|
29283
|
+
if (start === -1)
|
|
29284
|
+
return {};
|
|
29285
|
+
let depth = 0;
|
|
29286
|
+
for (let i = start; i < text2.length; i++) {
|
|
29287
|
+
if (text2[i] === "{")
|
|
29288
|
+
depth++;
|
|
29289
|
+
else if (text2[i] === "}") {
|
|
29290
|
+
depth--;
|
|
29291
|
+
if (depth === 0) {
|
|
29292
|
+
try {
|
|
29293
|
+
return JSON.parse(text2.slice(start, i + 1));
|
|
29294
|
+
} catch {
|
|
29295
|
+
return {};
|
|
29296
|
+
}
|
|
29297
|
+
}
|
|
29298
|
+
}
|
|
29299
|
+
}
|
|
29300
|
+
return {};
|
|
29301
|
+
}
|
|
29302
|
+
|
|
29303
|
+
// ../../packages/learning/dist/generator.js
|
|
29304
|
+
async function generateHunches(input) {
|
|
29305
|
+
const count = input.count ?? 20;
|
|
29306
|
+
const prompt = buildGenerationPrompt(input.profile, input.metricNames, count, input.contextBlock ?? "");
|
|
29307
|
+
const res = await input.provider.chat({ system: "You generate governed analytics questions and SQL.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
|
|
29308
|
+
const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29309
|
+
const parsed = extractJson(text2);
|
|
29310
|
+
const raw = Array.isArray(parsed.hunches) ? parsed.hunches : [];
|
|
29311
|
+
const hunches = [];
|
|
29312
|
+
for (const r of raw) {
|
|
29313
|
+
const sql = typeof r.sql === "string" ? r.sql : "";
|
|
29314
|
+
const question = typeof r.question === "string" ? r.question : "";
|
|
29315
|
+
if (!sql || !question || !isReadOnlySql(sql))
|
|
29316
|
+
continue;
|
|
29317
|
+
hunches.push({
|
|
29318
|
+
connection: input.connection,
|
|
29319
|
+
question,
|
|
29320
|
+
sql,
|
|
29321
|
+
generatedSql: sql,
|
|
29322
|
+
tablesUsed: Array.isArray(r.tablesUsed) ? r.tablesUsed : [],
|
|
29323
|
+
rationale: typeof r.rationale === "string" ? r.rationale : void 0,
|
|
29324
|
+
confidence: typeof r.confidence === "number" ? r.confidence : void 0
|
|
29325
|
+
});
|
|
29326
|
+
}
|
|
29327
|
+
return hunches;
|
|
29328
|
+
}
|
|
29329
|
+
|
|
29330
|
+
// ../../packages/learning/dist/blindspot.js
|
|
29331
|
+
function buildBlindspotPrompt(profile, metricNames, contextBlock, modelGraph) {
|
|
29332
|
+
const tables = profile.tables.map((t) => {
|
|
29333
|
+
const cols = t.columns.map((c) => {
|
|
29334
|
+
const top = c.topValues?.length ? ` values:[${c.topValues.slice(0, 6).map((v) => JSON.stringify(v.value)).join(", ")}]` : "";
|
|
29335
|
+
return ` - ${c.name} (${c.type})${top}`;
|
|
29336
|
+
}).join("\n");
|
|
29337
|
+
return ` ${t.table}:
|
|
29338
|
+
${cols}`;
|
|
29339
|
+
}).join("\n");
|
|
29340
|
+
return [
|
|
29341
|
+
`You are auditing a data warehouse to find where you LACK business context and where useful marts are missing.`,
|
|
29342
|
+
contextBlock ? `Known business context:
|
|
29343
|
+
${contextBlock}` : "",
|
|
29344
|
+
metricNames.length ? `Governed metrics: ${metricNames.join(", ")}.` : "",
|
|
29345
|
+
modelGraph ? `Model lineage (raw \u2192 staging \u2192 marts):
|
|
29346
|
+
${modelGraph}` : "",
|
|
29347
|
+
`Profiled tables:
|
|
29348
|
+
${tables}`,
|
|
29349
|
+
`Return ONLY JSON: {"blindspots":[{"kind":"context","title":string,"detail":string,"subject":string,"confidence":number} | {"kind":"opportunity","title":string,"detail":string,"suggestedModel":{"name":string,"description":string,"rationale":string,"columns":[{"name":string,"description":string}]},"confidence":number}]}.`,
|
|
29350
|
+
`"context" = an ambiguity you need explained to write correct SQL (use the ACTUAL values shown). "opportunity" = a useful mart that does not yet exist.`
|
|
29351
|
+
].filter(Boolean).join("\n\n");
|
|
29352
|
+
}
|
|
29353
|
+
async function scanBlindspots(input) {
|
|
29354
|
+
const prompt = buildBlindspotPrompt(input.profile, input.metricNames, input.contextBlock, input.modelGraph);
|
|
29355
|
+
const res = await input.provider.chat({ system: "You audit data warehouses for missing business context and missing marts.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 4e3 });
|
|
29356
|
+
const text2 = res.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29357
|
+
const parsed = extractJson(text2);
|
|
29358
|
+
const raw = Array.isArray(parsed.blindspots) ? parsed.blindspots : [];
|
|
29359
|
+
const items = [];
|
|
29360
|
+
for (const r of raw) {
|
|
29361
|
+
const candidate = { ...r, connection: input.connection };
|
|
29362
|
+
const result = NewBlindspotSchema.safeParse(candidate);
|
|
29363
|
+
if (result.success)
|
|
29364
|
+
items.push(result.data);
|
|
29365
|
+
}
|
|
29366
|
+
return items;
|
|
29367
|
+
}
|
|
29368
|
+
|
|
29369
|
+
// ../../packages/learning/dist/context.js
|
|
29370
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync } from "fs";
|
|
29371
|
+
import { join as join9 } from "path";
|
|
29372
|
+
var MAX_DOC = 6e3;
|
|
29373
|
+
var MAX_TOTAL = 16e3;
|
|
29374
|
+
function loadKnowledgeDocs(projectDir) {
|
|
29375
|
+
const dir = join9(projectDir, "knowledge");
|
|
29376
|
+
if (!existsSync2(dir))
|
|
29377
|
+
return [];
|
|
29378
|
+
return readdirSync(dir).filter((f) => /\.md$/i.test(f)).map((f) => readFileSync2(join9(dir, f), "utf8"));
|
|
29379
|
+
}
|
|
29380
|
+
function buildContextBlock(input) {
|
|
29381
|
+
const parts = [];
|
|
29382
|
+
for (const doc of input.docs) {
|
|
29383
|
+
const d = doc.length > MAX_DOC ? doc.slice(0, MAX_DOC) + "\n\u2026(truncated)" : doc;
|
|
29384
|
+
parts.push(d);
|
|
29385
|
+
}
|
|
29386
|
+
if (input.answeredBlindspots.length) {
|
|
29387
|
+
parts.push("Confirmed business context (from answered questions):");
|
|
29388
|
+
for (const a of input.answeredBlindspots)
|
|
29389
|
+
parts.push(`- ${a.title}: ${a.answer}`);
|
|
29390
|
+
}
|
|
29391
|
+
let block = parts.join("\n\n").trim();
|
|
29392
|
+
if (block.length > MAX_TOTAL)
|
|
29393
|
+
block = block.slice(0, MAX_TOTAL) + "\n\u2026(truncated)";
|
|
29394
|
+
return block;
|
|
29395
|
+
}
|
|
29396
|
+
|
|
29397
|
+
// ../../packages/learning/dist/retrieval.js
|
|
29398
|
+
function cosineSimilarity(a, b) {
|
|
29399
|
+
let dot = 0, na = 0, nb = 0;
|
|
29400
|
+
for (let i = 0; i < a.length; i++) {
|
|
29401
|
+
dot += a[i] * b[i];
|
|
29402
|
+
na += a[i] * a[i];
|
|
29403
|
+
nb += b[i] * b[i];
|
|
29404
|
+
}
|
|
29405
|
+
if (na === 0 || nb === 0)
|
|
29406
|
+
return 0;
|
|
29407
|
+
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
29408
|
+
}
|
|
29409
|
+
async function retrieveGrounding(input) {
|
|
29410
|
+
const { question, connection, store, embedder } = input;
|
|
29411
|
+
const topK = input.topK ?? 5;
|
|
29412
|
+
const corpus = [];
|
|
29413
|
+
for (const h of await store.listHunches({ connection, status: "confirmed" }))
|
|
29414
|
+
corpus.push({ kind: "hunch", id: h.id, text: h.question, payload: { sql: h.sql, title: h.question } });
|
|
29415
|
+
for (const b of await store.listBlindspots({ connection, status: "answered" }))
|
|
29416
|
+
if (b.answer)
|
|
29417
|
+
corpus.push({ kind: "blindspot", id: b.id, text: `${b.title}: ${b.answer}`, payload: { title: b.title } });
|
|
29418
|
+
for (const d of await store.listKnowledgeDocs({ connection }))
|
|
29419
|
+
corpus.push({ kind: "knowledge", id: d.id, text: `${d.title}
|
|
29420
|
+
${d.content}`, payload: { title: d.title } });
|
|
29421
|
+
if (corpus.length === 0)
|
|
29422
|
+
return [];
|
|
29423
|
+
const toEmbed = [];
|
|
29424
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
29425
|
+
for (const item of corpus) {
|
|
29426
|
+
const cached = await store.getEmbedding(item.kind, item.id);
|
|
29427
|
+
if (cached && cached.text === item.text)
|
|
29428
|
+
vectors.set(item.id, cached.vector);
|
|
29429
|
+
else
|
|
29430
|
+
toEmbed.push(item);
|
|
29431
|
+
}
|
|
29432
|
+
if (toEmbed.length) {
|
|
29433
|
+
const embs = await embedder.embed(toEmbed.map((i) => i.text));
|
|
29434
|
+
for (let i = 0; i < toEmbed.length; i++) {
|
|
29435
|
+
const item = toEmbed[i];
|
|
29436
|
+
const vec = embs[i];
|
|
29437
|
+
vectors.set(item.id, vec);
|
|
29438
|
+
await store.upsertEmbedding({ connection, source_kind: item.kind, source_id: item.id, text: item.text, vector: vec });
|
|
29439
|
+
}
|
|
29440
|
+
}
|
|
29441
|
+
const [qVec] = await embedder.embed([question]);
|
|
29442
|
+
const ranked = corpus.map((item) => ({ ...item, score: cosineSimilarity(qVec, vectors.get(item.id)) })).sort((a, b) => b.score - a.score).slice(0, topK);
|
|
29443
|
+
return ranked.map((r) => ({ kind: r.kind, id: r.id, text: r.text, score: r.score, payload: r.payload }));
|
|
29444
|
+
}
|
|
29445
|
+
|
|
29446
|
+
// ../../packages/learning/dist/answer.js
|
|
29447
|
+
function extractSql(text2) {
|
|
29448
|
+
const fence = text2.match(/```(?:sql)?\s*([\s\S]*?)```/i);
|
|
29449
|
+
return (fence ? fence[1] : text2).trim();
|
|
29450
|
+
}
|
|
29451
|
+
async function answerQuestion(input) {
|
|
29452
|
+
const { question, connection, store, embedder, provider, executeRawSql } = input;
|
|
29453
|
+
const reuseThreshold = input.reuseThreshold ?? 0.9;
|
|
29454
|
+
const top = await retrieveGrounding({ question, connection, store, embedder, topK: 5 });
|
|
29455
|
+
if (top.length === 0) {
|
|
29456
|
+
return { question, mode: "none", message: "No governed knowledge yet for this source \u2014 confirm some Hunches first." };
|
|
29457
|
+
}
|
|
29458
|
+
const groundedOn = top.map((t) => ({ kind: t.kind, title: t.payload.title, score: t.score }));
|
|
29459
|
+
const topHunch = top.find((t) => t.kind === "hunch");
|
|
29460
|
+
if (topHunch && topHunch === top[0] && topHunch.score >= reuseThreshold && topHunch.payload.sql) {
|
|
29461
|
+
try {
|
|
29462
|
+
const res = await executeRawSql(limit(topHunch.payload.sql));
|
|
29463
|
+
return { question, mode: "reused", sql: topHunch.payload.sql, columns: res.columns, rows: res.rows, groundedOn };
|
|
29464
|
+
} catch (e) {
|
|
29465
|
+
return { question, mode: "reused", sql: topHunch.payload.sql, groundedOn, message: `Query failed: ${e.message}` };
|
|
29466
|
+
}
|
|
29467
|
+
}
|
|
29468
|
+
const examples = top.filter((t) => t.kind === "hunch" && t.payload.sql).map((t) => `Q: ${t.payload.title}
|
|
29469
|
+
SQL: ${t.payload.sql}`).join("\n\n");
|
|
29470
|
+
const context = top.filter((t) => t.kind !== "hunch").map((t) => `- ${t.text}`).join("\n");
|
|
29471
|
+
const prompt = [
|
|
29472
|
+
`Write a single read-only SQL query that answers the question, using ONLY these trusted examples and business context.`,
|
|
29473
|
+
examples ? `Trusted examples:
|
|
29474
|
+
${examples}` : "",
|
|
29475
|
+
context ? `Business context:
|
|
29476
|
+
${context}` : "",
|
|
29477
|
+
`Question: ${question}`,
|
|
29478
|
+
`Respond with ONLY the SQL (a single SELECT).`
|
|
29479
|
+
].filter(Boolean).join("\n\n");
|
|
29480
|
+
const resp = await provider.chat({ system: "You write governed, read-only analytics SQL grounded in trusted examples.", messages: [{ role: "user", content: prompt }], tools: [], maxTokens: 1e3 });
|
|
29481
|
+
const text2 = resp.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
29482
|
+
const sql = extractSql(text2);
|
|
29483
|
+
if (!isReadOnlySql(sql)) {
|
|
29484
|
+
return { question, mode: "generated", sql, groundedOn, message: "Generated SQL was not read-only; refusing to run it." };
|
|
29485
|
+
}
|
|
29486
|
+
try {
|
|
29487
|
+
const res = await executeRawSql(limit(sql));
|
|
29488
|
+
return { question, mode: "generated", sql, columns: res.columns, rows: res.rows, groundedOn };
|
|
29489
|
+
} catch (e) {
|
|
29490
|
+
return { question, mode: "generated", sql, groundedOn, message: `Query failed: ${e.message}` };
|
|
29491
|
+
}
|
|
29492
|
+
}
|
|
29493
|
+
function limit(sql) {
|
|
29494
|
+
return /\blimit\b/i.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} LIMIT 1000`;
|
|
29495
|
+
}
|
|
29496
|
+
|
|
29497
|
+
// ../../packages/mcp-core/dist/tools/answer.js
|
|
29498
|
+
async function answerQuestionTool(deps, args) {
|
|
29499
|
+
const connection = args.connection ?? deps.defaultConnectionName ?? "default";
|
|
29500
|
+
const result = await answerQuestion({
|
|
29501
|
+
question: args.question,
|
|
29502
|
+
connection,
|
|
29503
|
+
store: deps.knowledgeStore,
|
|
29504
|
+
embedder: deps.embedder,
|
|
29505
|
+
provider: deps.provider,
|
|
29506
|
+
executeRawSql: (sql) => deps.queryService.executeRawSql(sql),
|
|
29507
|
+
connectionType: deps.connectionType
|
|
29508
|
+
});
|
|
29509
|
+
return JSON.stringify(result, null, 2);
|
|
29510
|
+
}
|
|
29511
|
+
|
|
28741
29512
|
// ../../packages/mcp-core/dist/render/embedResource.js
|
|
28742
29513
|
import { createHash } from "crypto";
|
|
28743
29514
|
import { createUIResource } from "@mcp-ui/server";
|
|
@@ -28845,7 +29616,10 @@ function createMcpServer(deps) {
|
|
|
28845
29616
|
inputSchema: { sql: z2.string(), limit: z2.number().optional() }
|
|
28846
29617
|
}, async (args) => text(await runSql(deps, args)));
|
|
28847
29618
|
server.registerTool("list_tables", { description: "List warehouse tables from the cached catalog (name, schema, connection, column count).", inputSchema: {} }, async () => text(await listTables(deps)));
|
|
28848
|
-
server.registerTool("describe_table", {
|
|
29619
|
+
server.registerTool("describe_table", {
|
|
29620
|
+
description: 'Show columns and types for a catalog table. Accepts { table } (with optional dot-notation schema: "schema.table") or { table, schema } separately.',
|
|
29621
|
+
inputSchema: { table: z2.string(), schema: z2.string().optional() }
|
|
29622
|
+
}, async (args) => text(await describeTable(deps, args)));
|
|
28849
29623
|
server.registerTool("sample_table", { description: "Return a few sample rows from a table.", inputSchema: { table: z2.string(), limit: z2.number().optional() } }, async (args) => text(await sampleTable(deps, args)));
|
|
28850
29624
|
server.registerTool("visualize", {
|
|
28851
29625
|
description: "Render a yamchart chart spec + data rows to an interactive chart (with a static image fallback). Provide a full chart spec (name, title, source.model, chart) and the data array.",
|
|
@@ -28873,6 +29647,66 @@ function createMcpServer(deps) {
|
|
|
28873
29647
|
description: "Save a yamchart chart spec to charts/<name>.yaml (requires editor/admin).",
|
|
28874
29648
|
inputSchema: { spec: z2.record(z2.unknown()) }
|
|
28875
29649
|
}, async (args) => text(await saveChart(deps, args)));
|
|
29650
|
+
if (deps.semanticLayer) {
|
|
29651
|
+
const semanticLayer = deps.semanticLayer;
|
|
29652
|
+
server.registerTool("list_metrics", {
|
|
29653
|
+
description: "List governed business metrics (the semantic glossary): name, synonyms, the entity they belong to, and which dimensions you can slice them by. Prefer querying these by name over writing raw SQL.",
|
|
29654
|
+
inputSchema: {}
|
|
29655
|
+
}, async () => text(listMetrics({ semanticLayer })));
|
|
29656
|
+
server.registerTool("get_metric", {
|
|
29657
|
+
description: "Get the full governed definition of a metric (by name or synonym): how it is computed (aggregation, field/sql, governed filter), its source table, format, and the dimensions/time dimension you can group it by.",
|
|
29658
|
+
inputSchema: { name: z2.string() }
|
|
29659
|
+
}, async (args) => text(getMetric({ semanticLayer }, args)));
|
|
29660
|
+
server.registerTool("query_metric", {
|
|
29661
|
+
description: "Query governed metrics by name (or synonym), optionally sliced by dimensions and a time grain, and filtered by a time range. Returns the data rows, the generated SQL (for transparency), and a ready-to-use suggested_chart spec. Prefer this over run_sql for business questions about governed metrics \u2014 it guarantees correct, governed definitions.",
|
|
29662
|
+
inputSchema: {
|
|
29663
|
+
metrics: z2.array(z2.string()).min(1),
|
|
29664
|
+
dimensions: z2.array(z2.string()).optional(),
|
|
29665
|
+
grain: z2.enum(["day", "week", "month", "quarter", "year"]).optional(),
|
|
29666
|
+
time_range: z2.union([
|
|
29667
|
+
z2.object({ preset: z2.string() }),
|
|
29668
|
+
z2.object({ start: z2.string(), end: z2.string() })
|
|
29669
|
+
]).optional(),
|
|
29670
|
+
filters: z2.array(z2.object({ dimension: z2.string(), operator: z2.string(), value: z2.any() })).optional(),
|
|
29671
|
+
order_by: z2.object({ field: z2.string(), direction: z2.enum(["asc", "desc"]).optional() }).optional(),
|
|
29672
|
+
limit: z2.number().int().positive().max(1e4).optional()
|
|
29673
|
+
}
|
|
29674
|
+
}, async (args) => {
|
|
29675
|
+
try {
|
|
29676
|
+
return text(await queryMetric({ semanticLayer, queryService: deps.queryService, connectionType: deps.connectionType }, args));
|
|
29677
|
+
} catch (err) {
|
|
29678
|
+
return text(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
29679
|
+
}
|
|
29680
|
+
});
|
|
29681
|
+
}
|
|
29682
|
+
if (deps.knowledgeStore && deps.embedder && deps.answerProvider) {
|
|
29683
|
+
const knowledgeStore = deps.knowledgeStore;
|
|
29684
|
+
const embedder = deps.embedder;
|
|
29685
|
+
const answerProvider = deps.answerProvider;
|
|
29686
|
+
server.registerTool("answer_question", {
|
|
29687
|
+
description: 'Answer a business question using governed knowledge: retrieves confirmed Hunches + business context for the source and either reuses a matching SQL or generates SQL grounded in trusted examples. Prefer this for "how many / how much / which" questions.',
|
|
29688
|
+
inputSchema: { question: z2.string(), connection: z2.string().optional() }
|
|
29689
|
+
}, async (args) => text(await answerQuestionTool({
|
|
29690
|
+
knowledgeStore,
|
|
29691
|
+
embedder,
|
|
29692
|
+
provider: answerProvider,
|
|
29693
|
+
queryService: deps.queryService,
|
|
29694
|
+
connectionType: deps.connectionType,
|
|
29695
|
+
defaultConnectionName: deps.aiConnectionName
|
|
29696
|
+
}, args)));
|
|
29697
|
+
}
|
|
29698
|
+
server.registerTool("get_chart_schema", {
|
|
29699
|
+
description: "Get the exact yamchart chart spec shape and a working example for a chart type (bar, line, area, kpi, pie, scatter, table). Call this before building a chart. Axes are objects: { field, type: temporal|quantitative|ordinal|nominal }.",
|
|
29700
|
+
inputSchema: { type: z2.string().optional() }
|
|
29701
|
+
}, async (args) => text(getChartSchema(args)));
|
|
29702
|
+
server.registerTool("validate_chart", {
|
|
29703
|
+
description: "Validate a yamchart chart spec without saving it. Returns { valid: true } or field-level errors with hints so you can self-correct before calling save_chart or visualize.",
|
|
29704
|
+
inputSchema: { spec: z2.record(z2.unknown()) }
|
|
29705
|
+
}, async (args) => text(validateChart(args)));
|
|
29706
|
+
server.registerTool("get_chart_spec", {
|
|
29707
|
+
description: "Get the full saved spec of an existing governed chart by name (so you can copy or adapt it). Call list_charts to discover names.",
|
|
29708
|
+
inputSchema: { name: z2.string() }
|
|
29709
|
+
}, async (args) => text(getChartSpec({ configLoader: deps.configLoader }, args)));
|
|
28876
29710
|
return server;
|
|
28877
29711
|
}
|
|
28878
29712
|
|
|
@@ -28884,13 +29718,19 @@ function getUser(request) {
|
|
|
28884
29718
|
return { id: user.id, email: user.email, role: user.role, attributes: user.attributes };
|
|
28885
29719
|
}
|
|
28886
29720
|
async function mcpRoutes(fastify, options) {
|
|
28887
|
-
const { configLoader, queryService, projectDir } = options;
|
|
29721
|
+
const { configLoader, queryService, projectDir, semanticLayer, connectionType, knowledgeStore, embedder, answerProvider, aiConnectionName } = options;
|
|
28888
29722
|
fastify.post("/mcp", async (request, reply) => {
|
|
28889
29723
|
const server = createMcpServer({
|
|
28890
29724
|
configLoader,
|
|
28891
29725
|
queryService,
|
|
28892
29726
|
projectDir,
|
|
28893
|
-
user: getUser(request)
|
|
29727
|
+
user: getUser(request),
|
|
29728
|
+
semanticLayer,
|
|
29729
|
+
connectionType,
|
|
29730
|
+
knowledgeStore,
|
|
29731
|
+
embedder,
|
|
29732
|
+
answerProvider,
|
|
29733
|
+
aiConnectionName
|
|
28894
29734
|
});
|
|
28895
29735
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
28896
29736
|
reply.raw.on("close", () => {
|
|
@@ -28914,7 +29754,7 @@ async function mcpRoutes(fastify, options) {
|
|
|
28914
29754
|
}
|
|
28915
29755
|
|
|
28916
29756
|
// ../server/dist/routes/catalog.js
|
|
28917
|
-
import { join as
|
|
29757
|
+
import { join as join10 } from "path";
|
|
28918
29758
|
import { readFile as readFile5 } from "fs/promises";
|
|
28919
29759
|
async function catalogRoutes(fastify, options) {
|
|
28920
29760
|
const { configLoader, projectDir } = options;
|
|
@@ -28927,7 +29767,7 @@ async function catalogRoutes(fastify, options) {
|
|
|
28927
29767
|
name: m.metadata.name,
|
|
28928
29768
|
description: m.metadata.description
|
|
28929
29769
|
}));
|
|
28930
|
-
const catalogPath =
|
|
29770
|
+
const catalogPath = join10(projectDir, ".yamchart", "catalog.json");
|
|
28931
29771
|
let tables = [];
|
|
28932
29772
|
try {
|
|
28933
29773
|
const catalogData = JSON.parse(await readFile5(catalogPath, "utf-8"));
|
|
@@ -28963,8 +29803,310 @@ async function catalogRoutes(fastify, options) {
|
|
|
28963
29803
|
});
|
|
28964
29804
|
}
|
|
28965
29805
|
|
|
29806
|
+
// ../server/dist/routes/learning.js
|
|
29807
|
+
var ReadOnlySqlError = class extends Error {
|
|
29808
|
+
constructor() {
|
|
29809
|
+
super("Only read-only SELECT queries can be confirmed.");
|
|
29810
|
+
this.name = "ReadOnlySqlError";
|
|
29811
|
+
}
|
|
29812
|
+
};
|
|
29813
|
+
async function confirmHunch(deps, id, body) {
|
|
29814
|
+
if (body.sql !== void 0 && !isReadOnlySql(body.sql)) {
|
|
29815
|
+
throw new ReadOnlySqlError();
|
|
29816
|
+
}
|
|
29817
|
+
return deps.store.updateHunch(id, { status: "confirmed", ...body.sql ? { sql: body.sql } : {} });
|
|
29818
|
+
}
|
|
29819
|
+
async function rejectHunch(deps, id) {
|
|
29820
|
+
return deps.store.updateHunch(id, { status: "rejected" });
|
|
29821
|
+
}
|
|
29822
|
+
async function runHunch(deps, id) {
|
|
29823
|
+
const hunch = await deps.store.getHunch(id);
|
|
29824
|
+
if (!hunch)
|
|
29825
|
+
return { error: "Hunch not found." };
|
|
29826
|
+
if (!isReadOnlySql(hunch.sql))
|
|
29827
|
+
return { error: "Only read-only SELECT queries can be run." };
|
|
29828
|
+
const limited = /\blimit\b/i.test(hunch.sql) ? hunch.sql : `${hunch.sql.replace(/;\s*$/, "")} LIMIT 50`;
|
|
29829
|
+
const res = await deps.queryService.executeRawSql(limited);
|
|
29830
|
+
const sample = { columns: res.columns, rows: res.rows, ranAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
29831
|
+
await deps.store.updateHunch(id, { lastRunSample: sample });
|
|
29832
|
+
return { sample };
|
|
29833
|
+
}
|
|
29834
|
+
async function answerBlindspot(deps, id, body) {
|
|
29835
|
+
return deps.store.updateBlindspot(id, { status: "answered", answer: body.answer });
|
|
29836
|
+
}
|
|
29837
|
+
async function dismissBlindspot(deps, id) {
|
|
29838
|
+
return deps.store.updateBlindspot(id, { status: "dismissed" });
|
|
29839
|
+
}
|
|
29840
|
+
async function loadContextBlock(opts, store, connection) {
|
|
29841
|
+
const fileDocs = loadKnowledgeDocs(opts.projectDir);
|
|
29842
|
+
const dbDocs = (await store.listKnowledgeDocs({ connection })).map((d) => `${d.title}
|
|
29843
|
+
${d.content}`);
|
|
29844
|
+
const answered = (await store.listBlindspots({ connection, status: "answered" })).filter((b) => b.answer).map((b) => ({ title: b.title, answer: b.answer }));
|
|
29845
|
+
return buildContextBlock({ docs: [...fileDocs, ...dbDocs], answeredBlindspots: answered });
|
|
29846
|
+
}
|
|
29847
|
+
async function learningRoutes(fastify, opts) {
|
|
29848
|
+
const { store, queryService } = opts;
|
|
29849
|
+
const requireStore = (reply) => {
|
|
29850
|
+
if (!store) {
|
|
29851
|
+
reply.code(503).send({ error: "Knowledge features require a configured database (set KNOWLEDGE_DATABASE_URL)." });
|
|
29852
|
+
return false;
|
|
29853
|
+
}
|
|
29854
|
+
return true;
|
|
29855
|
+
};
|
|
29856
|
+
fastify.post("/api/learn", async (request, reply) => {
|
|
29857
|
+
if (!requireStore(reply))
|
|
29858
|
+
return;
|
|
29859
|
+
if (!opts.canWrite(request)) {
|
|
29860
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29861
|
+
return;
|
|
29862
|
+
}
|
|
29863
|
+
const connection = request.body?.connection ?? opts.defaultConnectionName;
|
|
29864
|
+
reply.raw.writeHead(200, {
|
|
29865
|
+
"Content-Type": "text/event-stream",
|
|
29866
|
+
"Cache-Control": "no-cache",
|
|
29867
|
+
Connection: "keep-alive"
|
|
29868
|
+
});
|
|
29869
|
+
const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
|
|
29870
|
+
try {
|
|
29871
|
+
send("status", { phase: "profiling" });
|
|
29872
|
+
const tables = await opts.getConnectionTables(connection);
|
|
29873
|
+
const profile = await buildSourceProfile({
|
|
29874
|
+
connection,
|
|
29875
|
+
dialect: opts.dialect,
|
|
29876
|
+
tables,
|
|
29877
|
+
execute: (sql) => queryService.executeRawSql(sql)
|
|
29878
|
+
});
|
|
29879
|
+
await store.saveProfile(connection, profile);
|
|
29880
|
+
send("status", { phase: "generating" });
|
|
29881
|
+
let provider;
|
|
29882
|
+
try {
|
|
29883
|
+
provider = createProviderForTask(opts.aiConfig, "hunch_generation", process.env);
|
|
29884
|
+
} catch (e) {
|
|
29885
|
+
send("error", { message: e.message });
|
|
29886
|
+
reply.raw.end();
|
|
29887
|
+
return;
|
|
29888
|
+
}
|
|
29889
|
+
const contextBlock = await loadContextBlock(opts, store, connection);
|
|
29890
|
+
const candidates = await generateHunches({
|
|
29891
|
+
provider,
|
|
29892
|
+
profile,
|
|
29893
|
+
metricNames: opts.metricNames,
|
|
29894
|
+
connection,
|
|
29895
|
+
contextBlock
|
|
29896
|
+
});
|
|
29897
|
+
const created = await store.createHunches(candidates);
|
|
29898
|
+
send("done", { hunches: created });
|
|
29899
|
+
} catch (e) {
|
|
29900
|
+
send("error", { message: e.message });
|
|
29901
|
+
}
|
|
29902
|
+
reply.raw.end();
|
|
29903
|
+
});
|
|
29904
|
+
fastify.get("/api/hunches", async (request, reply) => {
|
|
29905
|
+
if (!requireStore(reply))
|
|
29906
|
+
return;
|
|
29907
|
+
const q = request.query;
|
|
29908
|
+
return store.listHunches({ connection: q.connection, status: q.status });
|
|
29909
|
+
});
|
|
29910
|
+
fastify.post("/api/hunches/:id/confirm", async (request, reply) => {
|
|
29911
|
+
if (!requireStore(reply))
|
|
29912
|
+
return;
|
|
29913
|
+
if (!opts.canWrite(request)) {
|
|
29914
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29915
|
+
return;
|
|
29916
|
+
}
|
|
29917
|
+
try {
|
|
29918
|
+
return await confirmHunch({ store, queryService }, request.params.id, request.body ?? {});
|
|
29919
|
+
} catch (e) {
|
|
29920
|
+
if (e instanceof ReadOnlySqlError) {
|
|
29921
|
+
reply.code(400).send({ error: e.message });
|
|
29922
|
+
return;
|
|
29923
|
+
}
|
|
29924
|
+
throw e;
|
|
29925
|
+
}
|
|
29926
|
+
});
|
|
29927
|
+
fastify.post("/api/hunches/:id/reject", async (request, reply) => {
|
|
29928
|
+
if (!requireStore(reply))
|
|
29929
|
+
return;
|
|
29930
|
+
if (!opts.canWrite(request)) {
|
|
29931
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29932
|
+
return;
|
|
29933
|
+
}
|
|
29934
|
+
return rejectHunch({ store, queryService }, request.params.id);
|
|
29935
|
+
});
|
|
29936
|
+
fastify.post("/api/hunches/:id/run", async (request, reply) => {
|
|
29937
|
+
if (!requireStore(reply))
|
|
29938
|
+
return;
|
|
29939
|
+
if (!opts.canWrite(request)) {
|
|
29940
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29941
|
+
return;
|
|
29942
|
+
}
|
|
29943
|
+
return runHunch({ store, queryService }, request.params.id);
|
|
29944
|
+
});
|
|
29945
|
+
fastify.post("/api/blindspots/scan", async (request, reply) => {
|
|
29946
|
+
if (!requireStore(reply))
|
|
29947
|
+
return;
|
|
29948
|
+
if (!opts.canWrite(request)) {
|
|
29949
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
29950
|
+
return;
|
|
29951
|
+
}
|
|
29952
|
+
const connection = request.body?.connection ?? opts.defaultConnectionName;
|
|
29953
|
+
reply.raw.writeHead(200, {
|
|
29954
|
+
"Content-Type": "text/event-stream",
|
|
29955
|
+
"Cache-Control": "no-cache",
|
|
29956
|
+
Connection: "keep-alive"
|
|
29957
|
+
});
|
|
29958
|
+
const send = (type, data) => reply.raw.write(formatSSEEvent({ type, data }));
|
|
29959
|
+
try {
|
|
29960
|
+
send("status", { phase: "scanning" });
|
|
29961
|
+
let profile = await store.getProfile(connection);
|
|
29962
|
+
if (!profile) {
|
|
29963
|
+
const tables = await opts.getConnectionTables(connection);
|
|
29964
|
+
profile = await buildSourceProfile({
|
|
29965
|
+
connection,
|
|
29966
|
+
dialect: opts.dialect,
|
|
29967
|
+
tables,
|
|
29968
|
+
execute: (sql) => queryService.executeRawSql(sql)
|
|
29969
|
+
});
|
|
29970
|
+
await store.saveProfile(connection, profile);
|
|
29971
|
+
}
|
|
29972
|
+
const contextBlock = await loadContextBlock(opts, store, connection);
|
|
29973
|
+
let provider;
|
|
29974
|
+
try {
|
|
29975
|
+
provider = createProviderForTask(opts.aiConfig, "blindspot_scan", process.env);
|
|
29976
|
+
} catch (e) {
|
|
29977
|
+
send("error", { message: e.message });
|
|
29978
|
+
reply.raw.end();
|
|
29979
|
+
return;
|
|
29980
|
+
}
|
|
29981
|
+
const items = await scanBlindspots({
|
|
29982
|
+
provider,
|
|
29983
|
+
profile,
|
|
29984
|
+
metricNames: opts.metricNames,
|
|
29985
|
+
contextBlock,
|
|
29986
|
+
connection
|
|
29987
|
+
});
|
|
29988
|
+
await store.deleteOpenBlindspots(connection);
|
|
29989
|
+
const created = await store.createBlindspots(items);
|
|
29990
|
+
send("done", { blindspots: created });
|
|
29991
|
+
} catch (e) {
|
|
29992
|
+
send("error", { message: e.message });
|
|
29993
|
+
}
|
|
29994
|
+
reply.raw.end();
|
|
29995
|
+
});
|
|
29996
|
+
fastify.get("/api/blindspots", async (request, reply) => {
|
|
29997
|
+
if (!requireStore(reply))
|
|
29998
|
+
return;
|
|
29999
|
+
const q = request.query;
|
|
30000
|
+
return store.listBlindspots({ connection: q.connection, status: q.status, kind: q.kind });
|
|
30001
|
+
});
|
|
30002
|
+
fastify.post("/api/blindspots/:id/answer", async (request, reply) => {
|
|
30003
|
+
if (!requireStore(reply))
|
|
30004
|
+
return;
|
|
30005
|
+
if (!opts.canWrite(request)) {
|
|
30006
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30007
|
+
return;
|
|
30008
|
+
}
|
|
30009
|
+
return answerBlindspot({ store }, request.params.id, request.body ?? { answer: "" });
|
|
30010
|
+
});
|
|
30011
|
+
fastify.post("/api/blindspots/:id/dismiss", async (request, reply) => {
|
|
30012
|
+
if (!requireStore(reply))
|
|
30013
|
+
return;
|
|
30014
|
+
if (!opts.canWrite(request)) {
|
|
30015
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30016
|
+
return;
|
|
30017
|
+
}
|
|
30018
|
+
return dismissBlindspot({ store }, request.params.id);
|
|
30019
|
+
});
|
|
30020
|
+
}
|
|
30021
|
+
|
|
30022
|
+
// ../server/dist/routes/data.js
|
|
30023
|
+
async function createKnowledgeDocHandler(deps, body) {
|
|
30024
|
+
const [doc] = await deps.store.createKnowledgeDoc({ ...body, source: "ui" });
|
|
30025
|
+
return doc;
|
|
30026
|
+
}
|
|
30027
|
+
async function dataRoutes(fastify, opts) {
|
|
30028
|
+
const { store } = opts;
|
|
30029
|
+
fastify.get("/api/sources", async () => {
|
|
30030
|
+
const conns = opts.getConnections();
|
|
30031
|
+
return Promise.all(conns.map(async (c) => {
|
|
30032
|
+
let tables = 0;
|
|
30033
|
+
let pendingHunches = 0;
|
|
30034
|
+
let openBlindspots = 0;
|
|
30035
|
+
try {
|
|
30036
|
+
tables = (await opts.getConnectionTables(c.name)).length;
|
|
30037
|
+
} catch {
|
|
30038
|
+
}
|
|
30039
|
+
if (store) {
|
|
30040
|
+
pendingHunches = (await store.listHunches({ connection: c.name, status: "pending" })).length;
|
|
30041
|
+
openBlindspots = (await store.listBlindspots({ connection: c.name, status: "open" })).length;
|
|
30042
|
+
}
|
|
30043
|
+
return { name: c.name, type: c.type, counts: { tables, pendingHunches, openBlindspots } };
|
|
30044
|
+
}));
|
|
30045
|
+
});
|
|
30046
|
+
fastify.get("/api/sources/:connection/tables", async (request) => {
|
|
30047
|
+
const connection = request.params.connection;
|
|
30048
|
+
const tables = await opts.getConnectionTables(connection);
|
|
30049
|
+
const profile = store ? await store.getProfile(connection) : void 0;
|
|
30050
|
+
return tables.map((t) => {
|
|
30051
|
+
const pt = profile?.tables.find((p) => p.table === t.table);
|
|
30052
|
+
const columns = t.columns.map((c) => {
|
|
30053
|
+
const pc = pt?.columns.find((x) => x.name === c.name);
|
|
30054
|
+
return {
|
|
30055
|
+
name: c.name,
|
|
30056
|
+
type: c.type,
|
|
30057
|
+
nullRate: pc?.nullRate,
|
|
30058
|
+
distinctCount: pc?.distinctCount,
|
|
30059
|
+
topValues: pc?.topValues
|
|
30060
|
+
};
|
|
30061
|
+
});
|
|
30062
|
+
return { table: t.table, columns };
|
|
30063
|
+
});
|
|
30064
|
+
});
|
|
30065
|
+
const requireStore = (reply) => {
|
|
30066
|
+
if (!store) {
|
|
30067
|
+
reply.code(503).send({ error: "Knowledge features require a configured database." });
|
|
30068
|
+
return false;
|
|
30069
|
+
}
|
|
30070
|
+
return true;
|
|
30071
|
+
};
|
|
30072
|
+
fastify.get("/api/knowledge", async (request, reply) => {
|
|
30073
|
+
if (!requireStore(reply))
|
|
30074
|
+
return;
|
|
30075
|
+
return store.listKnowledgeDocs({ connection: request.query.connection });
|
|
30076
|
+
});
|
|
30077
|
+
fastify.post("/api/knowledge", async (request, reply) => {
|
|
30078
|
+
if (!requireStore(reply))
|
|
30079
|
+
return;
|
|
30080
|
+
if (!opts.canWrite(request)) {
|
|
30081
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30082
|
+
return;
|
|
30083
|
+
}
|
|
30084
|
+
return createKnowledgeDocHandler({ store }, request.body);
|
|
30085
|
+
});
|
|
30086
|
+
fastify.put("/api/knowledge/:id", async (request, reply) => {
|
|
30087
|
+
if (!requireStore(reply))
|
|
30088
|
+
return;
|
|
30089
|
+
if (!opts.canWrite(request)) {
|
|
30090
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30091
|
+
return;
|
|
30092
|
+
}
|
|
30093
|
+
const { id } = request.params;
|
|
30094
|
+
return store.updateKnowledgeDoc(id, request.body);
|
|
30095
|
+
});
|
|
30096
|
+
fastify.delete("/api/knowledge/:id", async (request, reply) => {
|
|
30097
|
+
if (!requireStore(reply))
|
|
30098
|
+
return;
|
|
30099
|
+
if (!opts.canWrite(request)) {
|
|
30100
|
+
reply.code(403).send({ error: "Requires editor or admin role." });
|
|
30101
|
+
return;
|
|
30102
|
+
}
|
|
30103
|
+
await store.deleteKnowledgeDoc(request.params.id);
|
|
30104
|
+
return { ok: true };
|
|
30105
|
+
});
|
|
30106
|
+
}
|
|
30107
|
+
|
|
28966
30108
|
// ../server/dist/oauth/service.js
|
|
28967
|
-
import { randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
30109
|
+
import { randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
28968
30110
|
|
|
28969
30111
|
// ../server/dist/oauth/errors.js
|
|
28970
30112
|
var OAuthError = class extends Error {
|
|
@@ -29032,7 +30174,7 @@ var OAuthService = class {
|
|
|
29032
30174
|
}
|
|
29033
30175
|
const authMethod = input.token_endpoint_auth_method === "client_secret_post" ? "client_secret_post" : "none";
|
|
29034
30176
|
const grantTypes = input.grant_types?.length ? input.grant_types : ["authorization_code", "refresh_token"];
|
|
29035
|
-
const clientId =
|
|
30177
|
+
const clientId = randomUUID3();
|
|
29036
30178
|
let clientSecret;
|
|
29037
30179
|
let clientSecretHash = null;
|
|
29038
30180
|
if (authMethod === "client_secret_post") {
|
|
@@ -29489,17 +30631,17 @@ data: ${data}
|
|
|
29489
30631
|
}
|
|
29490
30632
|
|
|
29491
30633
|
// ../server/dist/server.js
|
|
29492
|
-
import { join as
|
|
30634
|
+
import { join as join11, dirname as dirname2 } from "path";
|
|
29493
30635
|
import { fileURLToPath } from "url";
|
|
29494
30636
|
import { access as access2, readFile as readFile6 } from "fs/promises";
|
|
29495
30637
|
import { watch as fsWatch } from "fs";
|
|
29496
30638
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
29497
|
-
var packageJsonPath =
|
|
30639
|
+
var packageJsonPath = join11(__dirname, "..", "package.json");
|
|
29498
30640
|
var packageJson = JSON.parse(await readFile6(packageJsonPath, "utf-8"));
|
|
29499
30641
|
var VERSION = packageJson.version;
|
|
29500
30642
|
async function enrichRefsFromCatalog(projectDir, refs) {
|
|
29501
30643
|
try {
|
|
29502
|
-
const catalogPath =
|
|
30644
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
29503
30645
|
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
29504
30646
|
for (const model of catalogData.models ?? []) {
|
|
29505
30647
|
if (model.name && model.table) {
|
|
@@ -29510,7 +30652,7 @@ async function enrichRefsFromCatalog(projectDir, refs) {
|
|
|
29510
30652
|
}
|
|
29511
30653
|
}
|
|
29512
30654
|
async function createServer(options) {
|
|
29513
|
-
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir =
|
|
30655
|
+
const { projectDir, port = 3001, host = "0.0.0.0", watch: watch2 = false, serveStatic = process.env.NODE_ENV === "production", staticDir = join11(__dirname, "public"), auth } = options;
|
|
29514
30656
|
if (auth) {
|
|
29515
30657
|
initAuthServer(auth.supabaseUrl, auth.supabaseServiceKey);
|
|
29516
30658
|
}
|
|
@@ -29554,6 +30696,19 @@ async function createServer(options) {
|
|
|
29554
30696
|
maxSize: 1e3,
|
|
29555
30697
|
defaultTtlMs: cacheTtl
|
|
29556
30698
|
});
|
|
30699
|
+
const knowledgeUrlVar = project.knowledge?.database_url_var ?? "KNOWLEDGE_DATABASE_URL";
|
|
30700
|
+
const knowledgeDbUrl = process.env[knowledgeUrlVar];
|
|
30701
|
+
let knowledgeStore;
|
|
30702
|
+
if (knowledgeDbUrl) {
|
|
30703
|
+
const store = new PostgresKnowledgeStore(knowledgeDbUrl);
|
|
30704
|
+
try {
|
|
30705
|
+
await store.init();
|
|
30706
|
+
knowledgeStore = store;
|
|
30707
|
+
} catch (err) {
|
|
30708
|
+
fastify.log.warn(`Knowledge store init failed: ${err.message}`);
|
|
30709
|
+
knowledgeStore = void 0;
|
|
30710
|
+
}
|
|
30711
|
+
}
|
|
29557
30712
|
const models = {};
|
|
29558
30713
|
const refs = {};
|
|
29559
30714
|
for (const model of configLoader.getModels()) {
|
|
@@ -29700,16 +30855,103 @@ async function createServer(options) {
|
|
|
29700
30855
|
await fastify.register(catalogRoutes, { configLoader, projectDir });
|
|
29701
30856
|
}
|
|
29702
30857
|
const mcpBaseUrl = process.env.BASE_URL || project.defaults?.base_url || `http://localhost:${port}`;
|
|
30858
|
+
let semanticLayer;
|
|
30859
|
+
try {
|
|
30860
|
+
const semanticDefs = loadSemanticDefinitions(projectDir);
|
|
30861
|
+
if (semanticDefs.length > 0) {
|
|
30862
|
+
semanticLayer = buildSemanticLayer(semanticDefs, (model) => model);
|
|
30863
|
+
}
|
|
30864
|
+
} catch (err) {
|
|
30865
|
+
fastify.log.warn({ err }, "Failed to load curated semantic definitions; semantic MCP tools disabled");
|
|
30866
|
+
}
|
|
30867
|
+
const connectionType = configLoader.getDefaultConnection()?.type;
|
|
30868
|
+
let embedder;
|
|
30869
|
+
let answerProvider;
|
|
30870
|
+
try {
|
|
30871
|
+
embedder = createEmbedder(project.ai?.embedding, process.env);
|
|
30872
|
+
} catch (err) {
|
|
30873
|
+
fastify.log.warn({ err }, "Failed to build embedder; answer_question tool disabled");
|
|
30874
|
+
}
|
|
30875
|
+
try {
|
|
30876
|
+
answerProvider = createProviderForTask(project.ai, "answer", process.env);
|
|
30877
|
+
} catch (err) {
|
|
30878
|
+
fastify.log.warn({ err }, "Failed to build answer provider; answer_question tool disabled");
|
|
30879
|
+
}
|
|
30880
|
+
const aiConnectionName = defaultConnection?.name ?? "default";
|
|
30881
|
+
const mcpOptions = {
|
|
30882
|
+
configLoader,
|
|
30883
|
+
queryService,
|
|
30884
|
+
projectDir,
|
|
30885
|
+
semanticLayer,
|
|
30886
|
+
connectionType,
|
|
30887
|
+
knowledgeStore,
|
|
30888
|
+
embedder,
|
|
30889
|
+
answerProvider,
|
|
30890
|
+
aiConnectionName
|
|
30891
|
+
};
|
|
29703
30892
|
if (authDb && oauthService) {
|
|
29704
30893
|
const mcpAuth = createMcpAuthPreHandler({ authDb, oauthService, baseUrl: mcpBaseUrl });
|
|
29705
30894
|
await fastify.register(async (mcpScope) => {
|
|
29706
30895
|
mcpScope.addHook("preHandler", mcpAuth);
|
|
29707
|
-
await mcpScope.register(mcpRoutes,
|
|
30896
|
+
await mcpScope.register(mcpRoutes, mcpOptions);
|
|
29708
30897
|
});
|
|
29709
30898
|
} else {
|
|
29710
|
-
await fastify.register(mcpRoutes,
|
|
30899
|
+
await fastify.register(mcpRoutes, mcpOptions);
|
|
29711
30900
|
}
|
|
29712
|
-
const
|
|
30901
|
+
const learningOptions = {
|
|
30902
|
+
store: knowledgeStore,
|
|
30903
|
+
queryService,
|
|
30904
|
+
projectDir,
|
|
30905
|
+
defaultConnectionName: defaultConnection?.name ?? "default",
|
|
30906
|
+
dialect: defaultConnection?.type ?? "duckdb",
|
|
30907
|
+
metricNames: semanticLayer?.listMetrics().map((m) => m.name) ?? [],
|
|
30908
|
+
getConnectionTables: async (connection) => {
|
|
30909
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
30910
|
+
try {
|
|
30911
|
+
const catalogData = JSON.parse(await readFile6(catalogPath, "utf-8"));
|
|
30912
|
+
return (catalogData.models ?? []).filter((m) => !m.connection || m.connection === connection).map((m) => ({
|
|
30913
|
+
table: m.table ?? m.name ?? "",
|
|
30914
|
+
columns: (m.columns ?? []).map((c) => ({ name: c.name, type: c.data_type ?? "string" }))
|
|
30915
|
+
})).filter((t) => t.table && t.columns.length > 0);
|
|
30916
|
+
} catch {
|
|
30917
|
+
return [];
|
|
30918
|
+
}
|
|
30919
|
+
},
|
|
30920
|
+
// Writes (learn/confirm/reject) require editor/admin. When auth is disabled
|
|
30921
|
+
// there is no request.user, so dev/local mode permits writes.
|
|
30922
|
+
canWrite: (req) => {
|
|
30923
|
+
const user = req.user;
|
|
30924
|
+
if (!user)
|
|
30925
|
+
return true;
|
|
30926
|
+
return user.role !== "viewer";
|
|
30927
|
+
},
|
|
30928
|
+
aiConfig: project.ai
|
|
30929
|
+
};
|
|
30930
|
+
const dataOptions = {
|
|
30931
|
+
store: knowledgeStore,
|
|
30932
|
+
getConnections: () => configLoader.getConnections().map((c) => ({ name: c.name, type: c.type })),
|
|
30933
|
+
getConnectionTables: learningOptions.getConnectionTables,
|
|
30934
|
+
canWrite: learningOptions.canWrite
|
|
30935
|
+
};
|
|
30936
|
+
if (auth) {
|
|
30937
|
+
await fastify.register(async (protectedRoutes) => {
|
|
30938
|
+
protectedRoutes.addHook("preHandler", authMiddleware);
|
|
30939
|
+
protectedRoutes.addHook("preHandler", orgMiddleware);
|
|
30940
|
+
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30941
|
+
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
30942
|
+
});
|
|
30943
|
+
} else if (authDb) {
|
|
30944
|
+
const localMiddleware = createLocalAuthMiddleware(authDb);
|
|
30945
|
+
await fastify.register(async (protectedRoutes) => {
|
|
30946
|
+
protectedRoutes.addHook("preHandler", localMiddleware);
|
|
30947
|
+
await protectedRoutes.register(learningRoutes, learningOptions);
|
|
30948
|
+
await protectedRoutes.register(dataRoutes, dataOptions);
|
|
30949
|
+
});
|
|
30950
|
+
} else {
|
|
30951
|
+
await fastify.register(learningRoutes, learningOptions);
|
|
30952
|
+
await fastify.register(dataRoutes, dataOptions);
|
|
30953
|
+
}
|
|
30954
|
+
const assetsDir = join11(projectDir, "assets");
|
|
29713
30955
|
try {
|
|
29714
30956
|
await access2(assetsDir);
|
|
29715
30957
|
await fastify.register(fastifyStatic, {
|
|
@@ -29720,7 +30962,7 @@ async function createServer(options) {
|
|
|
29720
30962
|
} catch {
|
|
29721
30963
|
}
|
|
29722
30964
|
if (project.theme?.customCss) {
|
|
29723
|
-
const cssPath =
|
|
30965
|
+
const cssPath = join11(projectDir, project.theme.customCss);
|
|
29724
30966
|
fastify.get("/api/theme/custom.css", async (_request2, reply) => {
|
|
29725
30967
|
try {
|
|
29726
30968
|
const content = await readFile6(cssPath, "utf-8");
|
|
@@ -29779,7 +31021,7 @@ async function createServer(options) {
|
|
|
29779
31021
|
fastify.log.info(`Semantic model rebuilt: ${semanticService.getModel()?.entities.length ?? 0} entities`);
|
|
29780
31022
|
broadcastConfigChange();
|
|
29781
31023
|
});
|
|
29782
|
-
const catalogPath =
|
|
31024
|
+
const catalogPath = join11(projectDir, ".yamchart", "catalog.json");
|
|
29783
31025
|
try {
|
|
29784
31026
|
catalogWatcher = fsWatch(catalogPath, () => {
|
|
29785
31027
|
semanticService.buildFromDisk();
|
|
@@ -29843,15 +31085,15 @@ async function runDevServer(projectDir, options) {
|
|
|
29843
31085
|
let usesBrowserAuth = false;
|
|
29844
31086
|
let localAuth;
|
|
29845
31087
|
try {
|
|
29846
|
-
const { readFileSync:
|
|
31088
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
29847
31089
|
const { parse: parse2 } = await import("yaml");
|
|
29848
|
-
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-
|
|
29849
|
-
const raw =
|
|
31090
|
+
const { resolveProjectConfig: resolveProjectConfig2, deepMerge: deepMerge3 } = await import("./dist-PPAD6KOM.js");
|
|
31091
|
+
const raw = readFileSync3(resolve2(projectDir, "yamchart.yaml"), "utf-8");
|
|
29850
31092
|
const rawConfig = parse2(raw);
|
|
29851
31093
|
const resolvedEnv = options.env || process.env.YAMCHART_ENV || void 0;
|
|
29852
31094
|
let projectConfig = resolveProjectConfig2(rawConfig, resolvedEnv);
|
|
29853
31095
|
try {
|
|
29854
|
-
const localRaw =
|
|
31096
|
+
const localRaw = readFileSync3(resolve2(projectDir, "yamchart.local.yaml"), "utf-8");
|
|
29855
31097
|
const localOverrides = parse2(localRaw);
|
|
29856
31098
|
if (localOverrides && typeof localOverrides === "object") {
|
|
29857
31099
|
projectConfig = deepMerge3(projectConfig, localOverrides);
|
|
@@ -29869,7 +31111,7 @@ async function runDevServer(projectDir, options) {
|
|
|
29869
31111
|
const targetConn = process.env.YAMCHART_CONNECTION || projectConfig?.defaults?.connection;
|
|
29870
31112
|
if (targetConn) {
|
|
29871
31113
|
try {
|
|
29872
|
-
const connRaw =
|
|
31114
|
+
const connRaw = readFileSync3(resolve2(connectionDir, `${targetConn}.yaml`), "utf-8");
|
|
29873
31115
|
const connConfig = parse2(connRaw);
|
|
29874
31116
|
if (connConfig?.auth?.type === "externalbrowser") {
|
|
29875
31117
|
usesBrowserAuth = true;
|
|
@@ -29949,4 +31191,4 @@ async function runDevServer(projectDir, options) {
|
|
|
29949
31191
|
export {
|
|
29950
31192
|
runDevServer
|
|
29951
31193
|
};
|
|
29952
|
-
//# sourceMappingURL=dev-
|
|
31194
|
+
//# sourceMappingURL=dev-LZ4YHNDJ.js.map
|