@duckcodeailabs/dql-cli 1.6.5 → 1.6.6
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/apps-api.d.ts.map +1 -1
- package/dist/apps-api.js +59 -9
- package/dist/apps-api.js.map +1 -1
- package/dist/args.d.ts +11 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +21 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/{index-L-zyCapt.js → index-D_tpetmE.js} +346 -192
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent.d.ts +2 -2
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +78 -13
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +3 -2
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/compile.d.ts +2 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +33 -1
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +17 -3
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/llm/providers/dql-agent-provider.d.ts.map +1 -1
- package/dist/llm/providers/dql-agent-provider.js +113 -10
- package/dist/llm/providers/dql-agent-provider.js.map +1 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +222 -8
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/promote-from-draft.d.ts +4 -4
- package/dist/promote-from-draft.js +8 -8
- package/dist/promote-from-draft.js.map +1 -1
- package/package.json +11 -11
package/dist/local-runtime.js
CHANGED
|
@@ -8,7 +8,7 @@ import { loadSemanticLayerFromDir, resolveSemanticLayerAsync, getDialect, Parser
|
|
|
8
8
|
import { load as loadYaml } from 'js-yaml';
|
|
9
9
|
import { listBlockTemplates } from './block-templates.js';
|
|
10
10
|
import { getRunner as getLLMRunner } from './llm/index.js';
|
|
11
|
-
import { ClaudeProvider, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, defaultMemoryPath, ensureDefaultMemoryFiles, } from '@duckcodeailabs/dql-agent';
|
|
11
|
+
import { ClaudeProvider, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildLocalContextPack, defaultMemoryPath, ensureDefaultMemoryFiles, ensureMetadataCatalogFresh, recordRuntimeSchemaSnapshot, } from '@duckcodeailabs/dql-agent';
|
|
12
12
|
import { handleAppsApi } from './apps-api.js';
|
|
13
13
|
import { getEffectiveProviderConfig, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
|
|
14
14
|
import { DQLAccessDeniedError, activePersonaAppId, assertAppAccess, loadRuntimeApp, runtimeVariables, } from './governance-runtime.js';
|
|
@@ -17,6 +17,7 @@ import { Certifier } from '@duckcodeailabs/dql-governance';
|
|
|
17
17
|
import { buildSemanticObjectDetail, buildSemanticTree, computeSyncDiff, loadSemanticImportManifest, performSemanticImport, previewSemanticImport, syncSemanticImport, } from './semantic-import.js';
|
|
18
18
|
import { clearBlockStudioImportSessions, createBlockStudioImportSession, deleteBlockStudioImportSession, listBlockStudioImportSessions, loadBlockStudioImportSession, readBlockStudioImportCandidate, updateBlockStudioImportCandidate, writeBlockStudioImportSession, writeBlockStudioImportCandidate, } from './block-studio-import.js';
|
|
19
19
|
import { MetricFlowUnavailableError, compileMetricFlowQuery, hasDbtSemanticManifest, } from './metricflow.js';
|
|
20
|
+
const NOTEBOOK_EXECUTE_PREVIEW_ROW_LIMIT = 500;
|
|
20
21
|
export async function startLocalServer(opts) {
|
|
21
22
|
const { rootDir, executor, connection: rawConnection, preferredPort, projectRoot = process.cwd() } = opts;
|
|
22
23
|
const bindHost = opts.host ?? process.env.DQL_HOST ?? '127.0.0.1';
|
|
@@ -50,6 +51,7 @@ export async function startLocalServer(opts) {
|
|
|
50
51
|
catch { /* continue without */ }
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
53
55
|
// Auto-register data/ CSV and Parquet files as DuckDB views so semantic layer
|
|
54
56
|
// queries like `FROM orders` resolve without requiring read_csv_auto() in SQL.
|
|
55
57
|
if (connection.driver === 'file' || connection.driver === 'duckdb') {
|
|
@@ -234,13 +236,22 @@ export async function startLocalServer(opts) {
|
|
|
234
236
|
};
|
|
235
237
|
};
|
|
236
238
|
const getSchemaContextForAgent = async (question) => {
|
|
239
|
+
const catalogContext = await buildAgentSchemaContextFromCatalog(projectRoot, question).catch(() => []);
|
|
240
|
+
if (catalogContext.length > 0) {
|
|
241
|
+
const enriched = await enrichAgentSchemaContextWithValueMatches(question, catalogContext, executor, connection);
|
|
242
|
+
recordAgentRuntimeSchemaSnapshot(projectRoot, enriched, 'catalog enriched runtime schema');
|
|
243
|
+
return enriched;
|
|
244
|
+
}
|
|
237
245
|
try {
|
|
238
246
|
const result = await executor.executeQuery(`SELECT table_schema, table_name, column_name, data_type
|
|
239
247
|
FROM information_schema.columns
|
|
240
248
|
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
|
|
241
|
-
ORDER BY table_schema, table_name, ordinal_position
|
|
249
|
+
ORDER BY table_schema, table_name, ordinal_position
|
|
250
|
+
LIMIT 2000`, [], runtimeVariables({}), connection);
|
|
242
251
|
const schemaContext = buildAgentSchemaContext(question, result.rows);
|
|
243
|
-
|
|
252
|
+
const enriched = await enrichAgentSchemaContextWithValueMatches(question, schemaContext, executor, connection);
|
|
253
|
+
recordAgentRuntimeSchemaSnapshot(projectRoot, enriched, 'information_schema runtime scan');
|
|
254
|
+
return enriched;
|
|
244
255
|
}
|
|
245
256
|
catch {
|
|
246
257
|
return [];
|
|
@@ -1383,6 +1394,7 @@ export async function startLocalServer(opts) {
|
|
|
1383
1394
|
return;
|
|
1384
1395
|
}
|
|
1385
1396
|
setBlockStudioStatus(projectRoot, blockPath, newStatus);
|
|
1397
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
1386
1398
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1387
1399
|
res.end(serializeJSON({ ok: true, status: newStatus }));
|
|
1388
1400
|
}
|
|
@@ -1510,6 +1522,7 @@ export async function startLocalServer(opts) {
|
|
|
1510
1522
|
}
|
|
1511
1523
|
if (blockPath)
|
|
1512
1524
|
setBlockStudioStatus(projectRoot, blockPath, 'certified');
|
|
1525
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
1513
1526
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1514
1527
|
res.end(serializeJSON({ ok: true, status: 'certified', ...result }));
|
|
1515
1528
|
}
|
|
@@ -1613,6 +1626,8 @@ export async function startLocalServer(opts) {
|
|
|
1613
1626
|
}
|
|
1614
1627
|
const nextSession = { ...session, candidates: nextCandidates, updatedAt: new Date().toISOString() };
|
|
1615
1628
|
writeBlockStudioImportSession(projectRoot, nextSession);
|
|
1629
|
+
if (saved.length > 0)
|
|
1630
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
1616
1631
|
res.writeHead(errors.length > 0 ? 207 : 200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1617
1632
|
res.end(serializeJSON({ ok: errors.length === 0, session: nextSession, saved, errors }));
|
|
1618
1633
|
}
|
|
@@ -1730,6 +1745,7 @@ export async function startLocalServer(opts) {
|
|
|
1730
1745
|
});
|
|
1731
1746
|
const next = { ...readiness.candidate, reviewStatus: 'saved', savedPath };
|
|
1732
1747
|
writeBlockStudioImportCandidate(projectRoot, importId, next);
|
|
1748
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
1733
1749
|
const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
|
|
1734
1750
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1735
1751
|
res.end(serializeJSON({ candidate: next, block: payload }));
|
|
@@ -1868,6 +1884,7 @@ export async function startLocalServer(opts) {
|
|
|
1868
1884
|
}
|
|
1869
1885
|
: undefined,
|
|
1870
1886
|
});
|
|
1887
|
+
await refreshLocalMetadataCatalog(projectRoot);
|
|
1871
1888
|
const payload = openBlockStudioDocument(projectRoot, savedPath, semanticLayer);
|
|
1872
1889
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1873
1890
|
res.end(serializeJSON(payload));
|
|
@@ -3201,7 +3218,9 @@ export async function startLocalServer(opts) {
|
|
|
3201
3218
|
const resolved = resolveNotebookBlockReferenceCell(cell, projectRoot);
|
|
3202
3219
|
const executableCell = resolved.cell;
|
|
3203
3220
|
const cellConnection = isConnectionConfig(body.connection) ? body.connection : connection;
|
|
3204
|
-
const tableMapping =
|
|
3221
|
+
const tableMapping = needsSemanticTableMapping(executableCell)
|
|
3222
|
+
? await resolveSemanticTableMapping(executor, cellConnection, semanticLayer)
|
|
3223
|
+
: undefined;
|
|
3205
3224
|
const plan = buildExecutionPlan(executableCell, { semanticLayer, driver: cellConnection.driver, tableMapping });
|
|
3206
3225
|
if (!plan) {
|
|
3207
3226
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -3277,7 +3296,10 @@ table: ${table}${tagList}
|
|
|
3277
3296
|
return;
|
|
3278
3297
|
}
|
|
3279
3298
|
const content = readFileSync(filePath);
|
|
3280
|
-
res.writeHead(200, {
|
|
3299
|
+
res.writeHead(200, {
|
|
3300
|
+
'Content-Type': contentTypeFor(filePath),
|
|
3301
|
+
'Cache-Control': 'no-store, max-age=0',
|
|
3302
|
+
});
|
|
3281
3303
|
res.end(content);
|
|
3282
3304
|
});
|
|
3283
3305
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -3426,16 +3448,19 @@ function connectionDriverLabel(connection) {
|
|
|
3426
3448
|
function normalizeQueryResult(result, semanticRefs) {
|
|
3427
3449
|
const rawCols = Array.isArray(result?.columns) ? result.columns : [];
|
|
3428
3450
|
const columns = rawCols.map((c) => typeof c === 'string' ? c : typeof c?.name === 'string' ? c.name : String(c));
|
|
3451
|
+
const rawRows = Array.isArray(result?.rows) ? result.rows : [];
|
|
3452
|
+
const rows = rawRows.slice(0, NOTEBOOK_EXECUTE_PREVIEW_ROW_LIMIT);
|
|
3429
3453
|
const hasRefs = semanticRefs && (semanticRefs.metrics.length > 0 || semanticRefs.dimensions.length > 0);
|
|
3430
3454
|
return {
|
|
3431
3455
|
columns,
|
|
3432
|
-
rows
|
|
3433
|
-
rowCount: typeof result?.rowCount === 'number' ? result.rowCount :
|
|
3456
|
+
rows,
|
|
3457
|
+
rowCount: typeof result?.rowCount === 'number' ? result.rowCount : rawRows.length,
|
|
3434
3458
|
executionTime: typeof result?.executionTimeMs === 'number'
|
|
3435
3459
|
? result.executionTimeMs
|
|
3436
3460
|
: typeof result?.executionTime === 'number'
|
|
3437
3461
|
? result.executionTime
|
|
3438
3462
|
: 0,
|
|
3463
|
+
...(rawRows.length > rows.length ? { truncated: true } : {}),
|
|
3439
3464
|
...(hasRefs ? { semanticRefs } : {}),
|
|
3440
3465
|
};
|
|
3441
3466
|
}
|
|
@@ -3504,6 +3529,15 @@ export function serializeJSON(value) {
|
|
|
3504
3529
|
return current;
|
|
3505
3530
|
});
|
|
3506
3531
|
}
|
|
3532
|
+
async function refreshLocalMetadataCatalog(projectRoot) {
|
|
3533
|
+
try {
|
|
3534
|
+
await ensureMetadataCatalogFresh(projectRoot, { force: true });
|
|
3535
|
+
}
|
|
3536
|
+
catch {
|
|
3537
|
+
// The catalog is a rebuildable local cache. Save/certify flows should not
|
|
3538
|
+
// fail only because metadata refresh hit a stale dbt or semantic config.
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3507
3541
|
function renderNotFound(path) {
|
|
3508
3542
|
return `<!doctype html>
|
|
3509
3543
|
<html lang="en">
|
|
@@ -3866,6 +3900,13 @@ export function prepareSemanticSql(sql, semanticLayer) {
|
|
|
3866
3900
|
unresolvedRefs: resolution.unresolvedRefs,
|
|
3867
3901
|
};
|
|
3868
3902
|
}
|
|
3903
|
+
function needsSemanticTableMapping(cell) {
|
|
3904
|
+
if (cell.type === 'sql')
|
|
3905
|
+
return hasSemanticRefs(cell.source);
|
|
3906
|
+
if (cell.type !== 'dql')
|
|
3907
|
+
return false;
|
|
3908
|
+
return hasSemanticRefs(cell.source) || /\btype\s*=\s*"semantic"/i.test(cell.source);
|
|
3909
|
+
}
|
|
3869
3910
|
export function normalizeProjectConnection(connection, projectRoot) {
|
|
3870
3911
|
const normalized = expandConnectionEnvPlaceholders({ ...connection });
|
|
3871
3912
|
if ((normalized.driver === 'file' || normalized.driver === 'duckdb') && normalized.filepath && normalized.filepath !== ':memory:' && !isAbsoluteLikePath(normalized.filepath)) {
|
|
@@ -4318,7 +4359,9 @@ export async function resolveSemanticTableMapping(executor, connection, semantic
|
|
|
4318
4359
|
try {
|
|
4319
4360
|
const tablesResult = await executor.executeQuery(`SELECT table_schema, table_name
|
|
4320
4361
|
FROM information_schema.tables
|
|
4321
|
-
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
|
|
4362
|
+
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
|
|
4363
|
+
ORDER BY table_schema, table_name
|
|
4364
|
+
LIMIT 2000`, [], {}, connection);
|
|
4322
4365
|
return buildSemanticTableMapping(semanticLayer, tablesResult.rows);
|
|
4323
4366
|
}
|
|
4324
4367
|
catch {
|
|
@@ -6489,6 +6532,177 @@ function isAiPinRefreshDue(lastRefreshedAt) {
|
|
|
6489
6532
|
return true;
|
|
6490
6533
|
return Date.now() - last >= 24 * 60 * 60 * 1000;
|
|
6491
6534
|
}
|
|
6535
|
+
async function buildAgentSchemaContextFromCatalog(projectRoot, question) {
|
|
6536
|
+
const contextPack = await buildLocalContextPack(projectRoot, { question, limit: 80 });
|
|
6537
|
+
return buildAgentSchemaContextFromContextPack(question, contextPack);
|
|
6538
|
+
}
|
|
6539
|
+
function recordAgentRuntimeSchemaSnapshot(projectRoot, schemaContext, source) {
|
|
6540
|
+
if (schemaContext.length === 0)
|
|
6541
|
+
return;
|
|
6542
|
+
try {
|
|
6543
|
+
recordRuntimeSchemaSnapshot(projectRoot, {
|
|
6544
|
+
source,
|
|
6545
|
+
tables: schemaContext.slice(0, 80).map((table) => ({
|
|
6546
|
+
relation: table.relation,
|
|
6547
|
+
schema: table.schema,
|
|
6548
|
+
name: table.name,
|
|
6549
|
+
description: table.description,
|
|
6550
|
+
source: table.source,
|
|
6551
|
+
columns: table.columns.slice(0, 120).map((column) => ({
|
|
6552
|
+
name: column.name,
|
|
6553
|
+
type: column.type,
|
|
6554
|
+
description: column.description,
|
|
6555
|
+
sampleValues: column.sampleValues?.slice(0, 8),
|
|
6556
|
+
})),
|
|
6557
|
+
})),
|
|
6558
|
+
});
|
|
6559
|
+
}
|
|
6560
|
+
catch {
|
|
6561
|
+
// Runtime schema snapshots are advisory local metadata and must not block answers.
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
function buildAgentSchemaContextFromContextPack(question, contextPack) {
|
|
6565
|
+
const byRelation = new Map();
|
|
6566
|
+
const objectsByKey = new Map(contextPack.objects.map((object) => [object.objectKey, object]));
|
|
6567
|
+
const upsert = (table) => {
|
|
6568
|
+
if (!table.relation || !table.name)
|
|
6569
|
+
return;
|
|
6570
|
+
const key = table.relation.toLowerCase();
|
|
6571
|
+
const existing = byRelation.get(key);
|
|
6572
|
+
if (!existing) {
|
|
6573
|
+
byRelation.set(key, {
|
|
6574
|
+
...table,
|
|
6575
|
+
columns: dedupeAgentSchemaColumns(table.columns).slice(0, 80),
|
|
6576
|
+
});
|
|
6577
|
+
return;
|
|
6578
|
+
}
|
|
6579
|
+
byRelation.set(key, {
|
|
6580
|
+
...existing,
|
|
6581
|
+
description: existing.description ?? table.description,
|
|
6582
|
+
source: existing.source === table.source ? existing.source : 'local metadata catalog',
|
|
6583
|
+
columns: dedupeAgentSchemaColumns([...existing.columns, ...table.columns]).slice(0, 80),
|
|
6584
|
+
});
|
|
6585
|
+
};
|
|
6586
|
+
for (const object of contextPack.objects) {
|
|
6587
|
+
const table = metadataObjectToAgentSchemaTable(object);
|
|
6588
|
+
if (table)
|
|
6589
|
+
upsert(table);
|
|
6590
|
+
}
|
|
6591
|
+
for (const edge of contextPack.edges) {
|
|
6592
|
+
if (edge.edgeType !== 'maps_to_dbt_model' && edge.edgeType !== 'uses_dbt_model')
|
|
6593
|
+
continue;
|
|
6594
|
+
const from = objectsByKey.get(edge.fromKey);
|
|
6595
|
+
const to = objectsByKey.get(edge.toKey);
|
|
6596
|
+
const warehouse = from?.objectType === 'warehouse_table' ? from : null;
|
|
6597
|
+
const dbtModel = to && (to.objectType === 'dbt_model' || to.objectType === 'dbt_source') ? to : null;
|
|
6598
|
+
if (!warehouse || !dbtModel)
|
|
6599
|
+
continue;
|
|
6600
|
+
const warehouseTable = metadataObjectToAgentSchemaTable(warehouse);
|
|
6601
|
+
const modelTable = metadataObjectToAgentSchemaTable(dbtModel);
|
|
6602
|
+
if (!warehouseTable || !modelTable)
|
|
6603
|
+
continue;
|
|
6604
|
+
upsert({
|
|
6605
|
+
...warehouseTable,
|
|
6606
|
+
description: warehouseTable.description ?? modelTable.description,
|
|
6607
|
+
columns: modelTable.columns,
|
|
6608
|
+
source: 'local metadata catalog',
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
const tokens = agentSchemaTokens(question);
|
|
6612
|
+
const shouldProbeValues = extractAgentValueSearchTerms(question).length > 0;
|
|
6613
|
+
return Array.from(byRelation.values())
|
|
6614
|
+
.map((table) => ({
|
|
6615
|
+
table,
|
|
6616
|
+
score: scoreAgentSchemaTable(table, tokens) + (shouldProbeValues ? scoreAgentValueProbeTable(table) : 0),
|
|
6617
|
+
}))
|
|
6618
|
+
.filter((entry) => entry.table.columns.length > 0 && entry.score > 0)
|
|
6619
|
+
.sort((a, b) => b.score - a.score || a.table.relation.localeCompare(b.table.relation))
|
|
6620
|
+
.slice(0, 12)
|
|
6621
|
+
.map((entry) => entry.table);
|
|
6622
|
+
}
|
|
6623
|
+
function metadataObjectToAgentSchemaTable(object) {
|
|
6624
|
+
if (object.objectType === 'dbt_column' || object.objectType === 'runtime_column') {
|
|
6625
|
+
const relation = metadataPayloadString(object, 'relation');
|
|
6626
|
+
const model = metadataPayloadString(object, 'model') ?? relation;
|
|
6627
|
+
if (!model)
|
|
6628
|
+
return null;
|
|
6629
|
+
return {
|
|
6630
|
+
relation: relation ?? model,
|
|
6631
|
+
schema: relation ? relation.split('.').slice(-2, -1)[0] : undefined,
|
|
6632
|
+
name: relation ? relation.split('.').at(-1) ?? model : model,
|
|
6633
|
+
source: 'local metadata catalog',
|
|
6634
|
+
columns: [{
|
|
6635
|
+
name: object.name,
|
|
6636
|
+
type: metadataPayloadString(object, 'type'),
|
|
6637
|
+
description: object.description,
|
|
6638
|
+
}],
|
|
6639
|
+
};
|
|
6640
|
+
}
|
|
6641
|
+
if (object.objectType !== 'dbt_model' && object.objectType !== 'dbt_source' && object.objectType !== 'warehouse_table' && object.objectType !== 'runtime_table') {
|
|
6642
|
+
return null;
|
|
6643
|
+
}
|
|
6644
|
+
const relation = metadataObjectRelation(object);
|
|
6645
|
+
if (!relation)
|
|
6646
|
+
return null;
|
|
6647
|
+
const relationParts = relation.split('.').filter(Boolean);
|
|
6648
|
+
const schema = metadataPayloadString(object, 'schema') ?? (relationParts.length >= 2 ? relationParts[relationParts.length - 2] : undefined);
|
|
6649
|
+
const name = relationParts.at(-1) ?? object.name;
|
|
6650
|
+
const columns = metadataObjectColumns(object);
|
|
6651
|
+
return {
|
|
6652
|
+
relation,
|
|
6653
|
+
schema,
|
|
6654
|
+
name,
|
|
6655
|
+
description: object.description,
|
|
6656
|
+
columns,
|
|
6657
|
+
source: 'local metadata catalog',
|
|
6658
|
+
};
|
|
6659
|
+
}
|
|
6660
|
+
function metadataObjectRelation(object) {
|
|
6661
|
+
const relation = metadataPayloadString(object, 'relation');
|
|
6662
|
+
if (relation)
|
|
6663
|
+
return relation;
|
|
6664
|
+
const database = metadataPayloadString(object, 'database');
|
|
6665
|
+
const schema = metadataPayloadString(object, 'schema');
|
|
6666
|
+
if (database && schema)
|
|
6667
|
+
return [database, schema, object.name].join('.');
|
|
6668
|
+
return object.fullName ?? object.name;
|
|
6669
|
+
}
|
|
6670
|
+
function metadataObjectColumns(object) {
|
|
6671
|
+
const columns = object.payload?.columns;
|
|
6672
|
+
if (!Array.isArray(columns))
|
|
6673
|
+
return [];
|
|
6674
|
+
return columns.flatMap((column) => {
|
|
6675
|
+
if (!column || typeof column !== 'object')
|
|
6676
|
+
return [];
|
|
6677
|
+
const record = column;
|
|
6678
|
+
const name = stringFromRecord(record, 'name') ?? stringFromRecord(record, 'column_name');
|
|
6679
|
+
if (!name)
|
|
6680
|
+
return [];
|
|
6681
|
+
return [{
|
|
6682
|
+
name,
|
|
6683
|
+
type: stringFromRecord(record, 'type') ?? stringFromRecord(record, 'data_type'),
|
|
6684
|
+
description: stringFromRecord(record, 'description'),
|
|
6685
|
+
}];
|
|
6686
|
+
});
|
|
6687
|
+
}
|
|
6688
|
+
function metadataPayloadString(object, key) {
|
|
6689
|
+
const value = object.payload?.[key];
|
|
6690
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
6691
|
+
}
|
|
6692
|
+
function dedupeAgentSchemaColumns(columns) {
|
|
6693
|
+
const byName = new Map();
|
|
6694
|
+
for (const column of columns) {
|
|
6695
|
+
const key = column.name.toLowerCase();
|
|
6696
|
+
const existing = byName.get(key);
|
|
6697
|
+
byName.set(key, existing ? {
|
|
6698
|
+
...existing,
|
|
6699
|
+
type: existing.type ?? column.type,
|
|
6700
|
+
description: existing.description ?? column.description,
|
|
6701
|
+
sampleValues: uniqueStrings([...(existing.sampleValues ?? []), ...(column.sampleValues ?? [])]).slice(0, 5),
|
|
6702
|
+
} : column);
|
|
6703
|
+
}
|
|
6704
|
+
return Array.from(byName.values());
|
|
6705
|
+
}
|
|
6492
6706
|
export function buildAgentSchemaContext(question, rows) {
|
|
6493
6707
|
const byRelation = new Map();
|
|
6494
6708
|
for (const row of rows) {
|