mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-22 08:38:08 +02:00
fix(cli): make connection-not-configured errors actionable and expected (#301)
The MCP sql_execution/sl_query tools and the `ktx sql` CLI threw a plain Error naming no valid connection ids when an agent passed an unconfigured connectionId (or omitted it with multiple connections). The message reached the agent verbatim but gave it nothing to correct with, so it re-guessed for days, and each correct caller-driven rejection filed in PostHog Error Tracking as a ktx fault (issue 019eb10c, 8 occurrences on one install). Add a shared resolver (resolveConfiguredConnection / resolveRequiredConnectionId) that throws KtxExpectedError listing the configured connections, and route the three SQL-execution call sites through it. Expected-error classification keeps these out of Error Tracking while the actionable message lets agents self-correct.
This commit is contained in:
parent
9587049283
commit
8a50601582
8 changed files with 189 additions and 20 deletions
50
packages/cli/src/context/connections/resolve-connection.ts
Normal file
50
packages/cli/src/context/connections/resolve-connection.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { KtxExpectedError } from '../../errors.js';
|
||||
import type { KtxProjectConfig, KtxProjectConnectionConfig } from '../project/config.js';
|
||||
|
||||
function configuredConnectionIds(config: KtxProjectConfig): string[] {
|
||||
return Object.keys(config.connections).sort();
|
||||
}
|
||||
|
||||
function availableConnectionsHint(config: KtxProjectConfig): string {
|
||||
const ids = configuredConnectionIds(config);
|
||||
return ids.length === 0
|
||||
? 'No connections are configured in ktx.yaml.'
|
||||
: `Configured connections: ${ids.join(', ')}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a connection by id, throwing an expected (caller-driven) error that
|
||||
* names the configured connections so an agent or CLI user can self-correct.
|
||||
*/
|
||||
export function resolveConfiguredConnection(
|
||||
config: KtxProjectConfig,
|
||||
connectionId: string,
|
||||
): KtxProjectConnectionConfig {
|
||||
const connection = config.connections[connectionId];
|
||||
if (!connection) {
|
||||
throw new KtxExpectedError(
|
||||
`Connection "${connectionId}" is not configured in ktx.yaml. ${availableConnectionsHint(config)}`,
|
||||
);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the connection id to run against: validate a requested id against the
|
||||
* configured connections, or default to the sole connection when none is given.
|
||||
* Throws an expected error that lists the configured connections otherwise.
|
||||
*/
|
||||
export function resolveRequiredConnectionId(
|
||||
config: KtxProjectConfig,
|
||||
requested: string | undefined,
|
||||
): string {
|
||||
if (requested !== undefined) {
|
||||
resolveConfiguredConnection(config, requested);
|
||||
return requested;
|
||||
}
|
||||
const ids = configuredConnectionIds(config);
|
||||
if (ids.length === 1) {
|
||||
return ids[0];
|
||||
}
|
||||
throw new KtxExpectedError(`connectionId is required. ${availableConnectionsHint(config)}`);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import type { KtxSqlQueryExecutorPort } from '../../context/connections/query-executor.js';
|
||||
import { resolveConfiguredConnection } from '../../context/connections/resolve-connection.js';
|
||||
import { KtxQueryError, isNativeProgrammingFault } from '../../errors.js';
|
||||
import { localConnectionInfoFromConfig } from '../../context/connections/local-warehouse-descriptor.js';
|
||||
import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
|
||||
|
|
@ -39,10 +40,7 @@ async function executeValidatedReadOnlySql(
|
|||
): Promise<KtxSqlExecutionResponse> {
|
||||
await onProgress?.({ progress: 0, message: 'Validating SQL' });
|
||||
const connectionId = assertSafeConnectionId(input.connectionId);
|
||||
const connection = project.config.connections[connectionId];
|
||||
if (!connection) {
|
||||
throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`);
|
||||
}
|
||||
const connection = resolveConfiguredConnection(project.config, connectionId);
|
||||
if (!options.sqlAnalysis) {
|
||||
throw new Error('sql_execution requires parser-backed SQL validation.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { KtxSqlQueryExecutorPort } from '../../context/connections/query-ex
|
|||
import type { KtxSemanticLayerComputePort } from '../../context/daemon/semantic-layer-compute.js';
|
||||
import type { KtxMcpProgressCallback } from '../mcp/types.js';
|
||||
import type { KtxLocalProject } from '../../context/project/project.js';
|
||||
import { resolveRequiredConnectionId } from '../connections/resolve-connection.js';
|
||||
import { sqlAnalysisDialectForDriver } from '../sql-analysis/dialect.js';
|
||||
import { loadLocalSlSourceRecords } from './local-sl.js';
|
||||
import { toResolvedWire } from './semantic-layer.service.js';
|
||||
|
|
@ -27,14 +28,7 @@ export interface CompileLocalSlQueryResult extends SemanticLayerQueryExecutionRe
|
|||
}
|
||||
|
||||
function resolveLocalConnectionId(project: KtxLocalProject, requested: string | undefined): string {
|
||||
if (requested) {
|
||||
return assertSafeConnectionId(requested);
|
||||
}
|
||||
const ids = Object.keys(project.config.connections).sort();
|
||||
if (ids.length === 1) {
|
||||
return assertSafeConnectionId(ids[0]);
|
||||
}
|
||||
throw new Error('connectionId is required when the local project has zero or multiple connections.');
|
||||
return assertSafeConnectionId(resolveRequiredConnectionId(project.config, requested));
|
||||
}
|
||||
|
||||
async function loadComputableSources(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { resolveConfiguredConnection } from './context/connections/resolve-connection.js';
|
||||
import { loadKtxProject, type KtxLocalProject } from './context/project/project.js';
|
||||
import type { KtxQueryResult, KtxScanConnector } from './context/scan/types.js';
|
||||
import type { SqlAnalysisDialect, SqlAnalysisPort } from './context/sql-analysis/ports.js';
|
||||
|
|
@ -146,10 +147,7 @@ export async function runKtxSql(args: KtxSqlArgs, io: KtxCliIo = process, deps:
|
|||
let project: KtxLocalProject | undefined;
|
||||
try {
|
||||
project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
|
||||
const connection = project.config.connections[args.connectionId];
|
||||
if (!connection) {
|
||||
throw new Error(`Connection "${args.connectionId}" is not configured in ktx.yaml`);
|
||||
}
|
||||
const connection = resolveConfiguredConnection(project.config, args.connectionId);
|
||||
driver = String(connection.driver ?? 'unknown').toLowerCase();
|
||||
demoConnection = isDemoConnection(args.connectionId, connection);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue