mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
fix(context): report structured entity detail misses
This commit is contained in:
parent
47297bd25a
commit
f7245073df
2 changed files with 69 additions and 12 deletions
|
|
@ -143,6 +143,36 @@ describe('EntityDetailsTool', () => {
|
||||||
expect(result.structured.missing).toHaveLength(1);
|
expect(result.structured.missing).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reports missing structured table targets in model-visible markdown', async () => {
|
||||||
|
const result = await tool.call(
|
||||||
|
{
|
||||||
|
connectionName: 'warehouse',
|
||||||
|
targets: [{ catalog: null, db: 'public', name: 'orderz' }],
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.markdown).toContain('Not found in scan: public.orderz');
|
||||||
|
expect(result.markdown).toContain('Closest matches: orders');
|
||||||
|
expect(result.structured.resolved).toHaveLength(0);
|
||||||
|
expect(result.structured.missing).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports missing structured column targets in model-visible markdown', async () => {
|
||||||
|
const result = await tool.call(
|
||||||
|
{
|
||||||
|
connectionName: 'warehouse',
|
||||||
|
targets: [{ catalog: null, db: 'public', name: 'orders', column: 'plan_tier' }],
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.markdown).toContain('Column not found in scan: public.orders.plan_tier');
|
||||||
|
expect(result.markdown).toContain('Available columns: id, status');
|
||||||
|
expect(result.structured.resolved).toHaveLength(0);
|
||||||
|
expect(result.structured.missing).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns a no-scan state distinct from not found', async () => {
|
it('returns a no-scan state distinct from not found', async () => {
|
||||||
const result = await tool.call(
|
const result = await tool.call(
|
||||||
{ connectionName: 'empty', targets: [{ display: 'public.orders' }] },
|
{ connectionName: 'empty', targets: [{ display: 'public.orders' }] },
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const entityDetailsInputSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
type EntityDetailsInput = z.infer<typeof entityDetailsInputSchema>;
|
type EntityDetailsInput = z.infer<typeof entityDetailsInputSchema>;
|
||||||
|
type EntityDetailsTarget = EntityDetailsInput['targets'][number];
|
||||||
|
|
||||||
export interface EntityDetailsStructured {
|
export interface EntityDetailsStructured {
|
||||||
resolved: TableDetail[];
|
resolved: TableDetail[];
|
||||||
|
|
@ -30,6 +31,41 @@ function allowedConnectionNames(context: ToolContext): ReadonlySet<string> | nul
|
||||||
return context.session?.allowedConnectionNames ?? null;
|
return context.session?.allowedConnectionNames ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function targetLabel(target: EntityDetailsTarget): string {
|
||||||
|
if ('display' in target) {
|
||||||
|
return target.display;
|
||||||
|
}
|
||||||
|
return [target.catalog, target.db, target.name, target.column].filter((part): part is string => !!part).join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendMissingTargetMarkdown(parts: string[], target: EntityDetailsTarget, candidates: KtxTableRef[]): void {
|
||||||
|
parts.push(`Not found in scan: ${targetLabel(target)}`);
|
||||||
|
if (candidates.length > 0) {
|
||||||
|
parts.push(`Closest matches: ${candidates.map((candidate) => candidate.name).join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveTarget(
|
||||||
|
catalog: WarehouseCatalogService,
|
||||||
|
connectionName: string,
|
||||||
|
target: EntityDetailsTarget,
|
||||||
|
): Promise<{ resolved: (KtxTableRef & { column?: string }) | null; candidates: KtxTableRef[] }> {
|
||||||
|
if ('display' in target) {
|
||||||
|
return catalog.resolveDisplayTarget(connectionName, target.display);
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateResolution = await catalog.resolveDisplayTarget(connectionName, targetLabel(target));
|
||||||
|
return {
|
||||||
|
resolved: {
|
||||||
|
catalog: target.catalog,
|
||||||
|
db: target.db,
|
||||||
|
name: target.name,
|
||||||
|
column: target.column,
|
||||||
|
},
|
||||||
|
candidates: candidateResolution.candidates,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function sampleText(values: string[]): string {
|
function sampleText(values: string[]): string {
|
||||||
return values.length > 0 ? ` - sample: ${JSON.stringify(values.slice(0, 10))}` : '';
|
return values.length > 0 ? ` - sample: ${JSON.stringify(values.slice(0, 10))}` : '';
|
||||||
}
|
}
|
||||||
|
|
@ -92,25 +128,16 @@ export class EntityDetailsTool extends BaseTool<typeof entityDetailsInputSchema>
|
||||||
const missing: EntityDetailsStructured['missing'] = [];
|
const missing: EntityDetailsStructured['missing'] = [];
|
||||||
|
|
||||||
for (const target of input.targets) {
|
for (const target of input.targets) {
|
||||||
const resolution =
|
const resolution = await resolveTarget(catalog, input.connectionName, target);
|
||||||
'display' in target
|
|
||||||
? await catalog.resolveDisplayTarget(input.connectionName, target.display)
|
|
||||||
: {
|
|
||||||
resolved: { catalog: target.catalog, db: target.db, name: target.name, column: target.column },
|
|
||||||
candidates: [],
|
|
||||||
dialect: '',
|
|
||||||
};
|
|
||||||
if (!resolution.resolved) {
|
if (!resolution.resolved) {
|
||||||
missing.push({ target, candidates: resolution.candidates });
|
missing.push({ target, candidates: resolution.candidates });
|
||||||
parts.push(`Not found in scan: ${'display' in target ? target.display : target.name}`);
|
appendMissingTargetMarkdown(parts, target, resolution.candidates);
|
||||||
if (resolution.candidates.length > 0) {
|
|
||||||
parts.push(`Closest matches: ${resolution.candidates.map((candidate) => candidate.name).join(', ')}`);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const detail = await catalog.getTable({ connectionName: input.connectionName, ...resolution.resolved });
|
const detail = await catalog.getTable({ connectionName: input.connectionName, ...resolution.resolved });
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
missing.push({ target, candidates: resolution.candidates });
|
missing.push({ target, candidates: resolution.candidates });
|
||||||
|
appendMissingTargetMarkdown(parts, target, resolution.candidates);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const requestedColumn = resolution.resolved.column;
|
const requestedColumn = resolution.resolved.column;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue