@kaelio/ktx 0.11.0 → 0.13.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/assets/python/kaelio_ktx-0.13.0-py3-none-any.whl +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +3 -3
- package/dist/cli-runtime.js +2 -2
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +19 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/connection.js +15 -3
- package/dist/connectors/bigquery/connector.js +1 -14
- package/dist/connectors/clickhouse/connector.js +2 -16
- package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
- package/dist/connectors/duckdb/federated-attach.js +86 -0
- package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
- package/dist/connectors/duckdb/federated-executor.js +59 -0
- package/dist/connectors/mysql/connector.js +2 -16
- package/dist/connectors/postgres/connector.js +1 -14
- package/dist/connectors/shared/string-reference.d.ts +6 -0
- package/dist/connectors/shared/string-reference.js +19 -0
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/snowflake/connector.js +1 -14
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +4 -17
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/federation.d.ts +33 -0
- package/dist/context/connections/federation.js +51 -0
- package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
- package/dist/context/connections/project-sql-executor.d.ts +18 -0
- package/dist/context/connections/project-sql-executor.js +39 -0
- package/dist/context/connections/query-executor.d.ts +2 -2
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +119 -4
- package/dist/context/connections/resolve-connection.d.ts +12 -0
- package/dist/context/connections/resolve-connection.js +37 -0
- package/dist/context/core/git-env.d.ts +4 -0
- package/dist/context/core/git-env.js +5 -1
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +71 -8
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +3 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +19 -3
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +33 -8
- package/dist/context/mcp/local-project-ports.js +63 -89
- package/dist/context/mcp/types.d.ts +2 -0
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +33 -4
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +31 -44
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +71 -70
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +48 -0
- package/dist/context/sl/source-files.js +138 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest-query-executor.d.ts +2 -0
- package/dist/ingest-query-executor.js +8 -22
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +8 -0
- package/dist/io/tty.js +16 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.d.ts +21 -15
- package/dist/setup-agents.js +143 -66
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.d.ts +3 -0
- package/dist/setup-databases.js +47 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -1
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +28 -12
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +14 -13
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +35 -53
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/events.d.ts +3 -2
- package/dist/telemetry/events.js +11 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +2 -1
- package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
|
@@ -189,7 +189,7 @@ export class AiSdkKtxLlmRuntime {
|
|
|
189
189
|
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
190
190
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
191
191
|
if (typeof result.text !== 'string') {
|
|
192
|
-
throw new Error('
|
|
192
|
+
throw new Error('ktx LLM text generation returned no text');
|
|
193
193
|
}
|
|
194
194
|
return result.text;
|
|
195
195
|
}
|
|
@@ -223,7 +223,7 @@ export class AiSdkKtxLlmRuntime {
|
|
|
223
223
|
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
224
224
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
225
225
|
if (result.output == null) {
|
|
226
|
-
throw new Error('
|
|
226
|
+
throw new Error('ktx LLM object generation returned no output');
|
|
227
227
|
}
|
|
228
228
|
return result.output;
|
|
229
229
|
}
|
|
@@ -89,7 +89,23 @@ function assertInitIsolation(message, allowedToolIds, expectedMcpServerNames) {
|
|
|
89
89
|
function expectedMcpServerNames(tools) {
|
|
90
90
|
return tools && Object.keys(tools).length > 0 ? new Set([KTX_MCP_SERVER_NAME]) : new Set();
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
// "session limit" is the Claude Code subscription cap ("You've hit your session
|
|
93
|
+
// limit · resets …"); the rest are transient 429-style throttling. All mean
|
|
94
|
+
// Claude Code authenticated successfully, so they must not be read as auth
|
|
95
|
+
// failures by the governor classifier or the auth probe.
|
|
96
|
+
const CLAUDE_RATE_LIMIT_ERROR_MARKERS = /\b429\b|rate limit|session limit|usage limit|too many requests|quota exceeded|overloaded|max_retries/i;
|
|
97
|
+
// The subscription cap is its own case: re-authenticating and retrying both fail
|
|
98
|
+
// until reset, so it gets a distinct message from transient rate limiting.
|
|
99
|
+
const CLAUDE_SESSION_LIMIT_MARKERS = /session limit|usage limit/i;
|
|
100
|
+
function describeClaudeProbeFailure(message) {
|
|
101
|
+
if (CLAUDE_SESSION_LIMIT_MARKERS.test(message)) {
|
|
102
|
+
return `Claude Code session limit reached. Wait for the reset shown, then rerun setup or the command. Details: ${message}`;
|
|
103
|
+
}
|
|
104
|
+
if (CLAUDE_RATE_LIMIT_ERROR_MARKERS.test(message)) {
|
|
105
|
+
return `Claude Code is rate limited. Retry shortly, then rerun setup or the command. Details: ${message}`;
|
|
106
|
+
}
|
|
107
|
+
return `Claude Code authentication is not usable. Authenticate Claude Code locally with the Claude Code CLI, then rerun setup or the command. ${message}`;
|
|
108
|
+
}
|
|
93
109
|
function normalizeClaudeResetAtMs(value) {
|
|
94
110
|
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
95
111
|
return Math.round(value < 10_000_000_000 ? value * 1_000 : value);
|
|
@@ -176,7 +192,7 @@ function baseOptions(input) {
|
|
|
176
192
|
? { behavior: 'allow', toolUseID: options.toolUseID }
|
|
177
193
|
: {
|
|
178
194
|
behavior: 'deny',
|
|
179
|
-
message: `
|
|
195
|
+
message: `ktx claude-code runtime only permits current ktx MCP tools; denied ${toolName}.`,
|
|
180
196
|
toolUseID: options.toolUseID,
|
|
181
197
|
},
|
|
182
198
|
permissionMode: 'dontAsk',
|
|
@@ -402,7 +418,7 @@ export async function runClaudeCodeAuthProbe(input) {
|
|
|
402
418
|
const message = error instanceof Error ? error.message : String(error);
|
|
403
419
|
return {
|
|
404
420
|
ok: false,
|
|
405
|
-
message:
|
|
421
|
+
message: describeClaudeProbeFailure(message),
|
|
406
422
|
};
|
|
407
423
|
}
|
|
408
424
|
}
|
|
@@ -143,7 +143,7 @@ export function resolveLocalKtxEmbeddingConfig(config, env) {
|
|
|
143
143
|
batchSize: config.batchSize,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
throw new Error(`Unsupported
|
|
146
|
+
throw new Error(`Unsupported ktx embedding backend: ${String(config.backend)}`);
|
|
147
147
|
}
|
|
148
148
|
/** @internal */
|
|
149
149
|
export function createLocalKtxEmbeddingProviderFromConfig(config, deps = {}) {
|
|
@@ -21,7 +21,7 @@ export function normalizeKtxRuntimeToolOutput(value) {
|
|
|
21
21
|
}
|
|
22
22
|
function assertObjectSchema(name, schema) {
|
|
23
23
|
if (!(schema instanceof z.ZodObject)) {
|
|
24
|
-
throw new Error(`
|
|
24
|
+
throw new Error(`ktx runtime tool "${name}" must use z.object input schema for claude-code`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
export function createAiSdkToolSet(tools = {}) {
|
|
@@ -57,7 +57,7 @@ export function createRuntimeToolDescriptorFromAiTool(name, aiSdkTool) {
|
|
|
57
57
|
inputSchema: aiSdkTool.inputSchema,
|
|
58
58
|
execute: async (input) => {
|
|
59
59
|
if (typeof aiSdkTool.execute !== 'function') {
|
|
60
|
-
throw new Error(`
|
|
60
|
+
throw new Error(`ktx runtime tool "${name}" has no execute function`);
|
|
61
61
|
}
|
|
62
62
|
return normalizeKtxRuntimeToolOutput(await aiSdkTool.execute(input, { toolCallId: `runtime-${name}` }));
|
|
63
63
|
},
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { emitTelemetryEvent, mcpTelemetrySampleRate, reportException, shouldEmitMcpTelemetry, } from '../../telemetry/index.js';
|
|
4
4
|
import { collectTelemetryRedactionSecrets } from '../../telemetry/redaction-secrets.js';
|
|
5
|
-
import { scrubErrorClass } from '../../telemetry/scrubber.js';
|
|
5
|
+
import { formatErrorDetail, scrubErrorClass } from '../../telemetry/scrubber.js';
|
|
6
6
|
const connectionIdSchema = z.string().min(1);
|
|
7
7
|
const unknownRecordSchema = z.record(z.string(), z.unknown());
|
|
8
8
|
const tableRefSchema = z.object({
|
|
@@ -24,16 +24,16 @@ const toolAnnotations = {
|
|
|
24
24
|
memory_ingest_status: { title: 'Memory Ingest Status', readOnlyHint: true, openWorldHint: false },
|
|
25
25
|
};
|
|
26
26
|
const toolDescriptions = {
|
|
27
|
-
connection_list: 'List configured read-only data connections available to this
|
|
28
|
-
discover_data: 'Search across
|
|
29
|
-
wiki_search: 'Search
|
|
30
|
-
wiki_read: 'Read a
|
|
27
|
+
connection_list: 'List configured read-only data connections available to this ktx project. Use this before connection-scoped tools when the project may have multiple warehouses. A "_ktx_federated" entry (when present) queries all its member databases together; use its id for cross-database joins.',
|
|
28
|
+
discover_data: 'Search across ktx wiki pages, semantic-layer sources, measures, dimensions, raw tables, and columns. Example: discover_data({ query: "monthly orders by customer", connectionId: "warehouse", kinds: ["sl_source", "table"] }).',
|
|
29
|
+
wiki_search: 'Search ktx wiki pages for reusable business context. Example: wiki_search({ query: "revenue recognition", limit: 5 }).',
|
|
30
|
+
wiki_read: 'Read a ktx wiki page by key returned from wiki_search. Example: wiki_read({ key: "global/revenue" }).',
|
|
31
31
|
entity_details: 'Read table and column metadata from the latest live-database scan snapshot. Example: entity_details({ connectionId: "warehouse", entities: [{ table: { catalog: null, db: "public", name: "orders" }, columns: ["id"] }] }).',
|
|
32
32
|
dictionary_search: 'Search profile-sampled warehouse values to locate likely source columns for business values. Example: dictionary_search({ values: ["Acme Corp"], connectionId: "warehouse" }).',
|
|
33
33
|
sl_read_source: 'Read a semantic-layer YAML source by connection id and source name. Example: sl_read_source({ connectionId: "warehouse", sourceName: "orders" }).',
|
|
34
34
|
sl_query: 'Execute a semantic-layer query and return headers, rows, and total row count, plus correctness notes (e.g. compile-only or fan-out) when relevant. The generated SQL and full query plan are omitted by default; request them with include: ["sql"] and/or include: ["plan"]. Example: sl_query({ connectionId: "warehouse", measures: ["orders.order_count"], dimensions: [{ field: "orders.created_at", granularity: "month" }], include: ["sql"] }).',
|
|
35
|
-
sql_execution: 'Execute one parser-validated read-only SQL query against a configured
|
|
36
|
-
memory_ingest: 'Ingest free-form markdown knowledge into durable
|
|
35
|
+
sql_execution: 'Execute one parser-validated read-only SQL query against a configured ktx connection. Example: sql_execution({ connectionId: "warehouse", sql: "select count(*) from public.orders", maxRows: 100 }).',
|
|
36
|
+
memory_ingest: 'Ingest free-form markdown knowledge into durable ktx memory. Use this for business rules, metric definitions, schema gotchas, recurring findings, or explicit user requests to remember something. Example: memory_ingest({ connectionId: "warehouse", content: "ARR is reported in cents in this warehouse." }).',
|
|
37
37
|
memory_ingest_status: 'Read the current or final status for a memory ingest run. Example: memory_ingest_status({ runId: "memory-run-1" }).',
|
|
38
38
|
};
|
|
39
39
|
const connectionListSchema = z.object({});
|
|
@@ -160,6 +160,8 @@ const connectionListOutputSchema = z.object({
|
|
|
160
160
|
id: z.string(),
|
|
161
161
|
name: z.string(),
|
|
162
162
|
connectionType: z.string(),
|
|
163
|
+
members: z.array(z.string()).optional(),
|
|
164
|
+
hint: z.string().optional(),
|
|
163
165
|
})),
|
|
164
166
|
});
|
|
165
167
|
const wikiSearchOutputSchema = z.object({
|
|
@@ -442,6 +444,25 @@ function clientTelemetryFields(getClientInfo) {
|
|
|
442
444
|
...(client?.version ? { mcpClientVersion: client.version } : {}),
|
|
443
445
|
};
|
|
444
446
|
}
|
|
447
|
+
// Tools registered via registerParsedTool catch their own errors and return an
|
|
448
|
+
// isError result, so the telemetry layer never sees the thrown Error. Recover
|
|
449
|
+
// the failure message from the result's text content (the same string the agent
|
|
450
|
+
// reads) so the outcome event is self-diagnosing.
|
|
451
|
+
function mcpErrorResultDetail(result) {
|
|
452
|
+
if (typeof result !== 'object' || result === null || !('content' in result)) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
const content = result.content;
|
|
456
|
+
if (!Array.isArray(content)) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const text = content
|
|
460
|
+
.map((block) => typeof block === 'object' && block !== null && typeof block.text === 'string'
|
|
461
|
+
? block.text
|
|
462
|
+
: '')
|
|
463
|
+
.join('\n');
|
|
464
|
+
return formatErrorDetail(text);
|
|
465
|
+
}
|
|
445
466
|
function instrumentMcpServer(server, telemetry) {
|
|
446
467
|
return {
|
|
447
468
|
registerTool(name, config, handler) {
|
|
@@ -451,6 +472,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
451
472
|
const result = await handler(input, context);
|
|
452
473
|
if (telemetry.io && telemetry.projectDir && shouldEmitMcpTelemetry()) {
|
|
453
474
|
const isError = typeof result === 'object' && result !== null && 'isError' in result && result.isError === true;
|
|
475
|
+
const errorDetail = isError ? mcpErrorResultDetail(result) : undefined;
|
|
454
476
|
await emitTelemetryEvent({
|
|
455
477
|
name: 'mcp_request_completed',
|
|
456
478
|
projectDir: telemetry.projectDir,
|
|
@@ -460,6 +482,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
460
482
|
outcome: isError ? 'error' : 'ok',
|
|
461
483
|
durationMs: Math.max(0, performance.now() - startedAt),
|
|
462
484
|
sampleRate: mcpTelemetrySampleRate(),
|
|
485
|
+
...(errorDetail ? { errorDetail } : {}),
|
|
463
486
|
...clientTelemetryFields(telemetry.getClientInfo),
|
|
464
487
|
},
|
|
465
488
|
});
|
|
@@ -483,6 +506,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
483
506
|
}
|
|
484
507
|
if (telemetry.io && telemetry.projectDir && shouldEmitMcpTelemetry()) {
|
|
485
508
|
const errorClass = scrubErrorClass(error);
|
|
509
|
+
const errorDetail = formatErrorDetail(error);
|
|
486
510
|
await emitTelemetryEvent({
|
|
487
511
|
name: 'mcp_request_completed',
|
|
488
512
|
projectDir: telemetry.projectDir,
|
|
@@ -491,6 +515,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
491
515
|
toolName: name,
|
|
492
516
|
outcome: 'error',
|
|
493
517
|
...(errorClass ? { errorClass } : {}),
|
|
518
|
+
...(errorDetail ? { errorDetail } : {}),
|
|
494
519
|
durationMs: Math.max(0, performance.now() - startedAt),
|
|
495
520
|
sampleRate: mcpTelemetrySampleRate(),
|
|
496
521
|
...clientTelemetryFields(telemetry.getClientInfo),
|
|
@@ -641,7 +666,7 @@ export function registerKtxContextTools(deps) {
|
|
|
641
666
|
const ingestInput = {
|
|
642
667
|
userId: userContext.userId,
|
|
643
668
|
chatId: `mcp-${randomUUID()}`,
|
|
644
|
-
userMessage: 'Ingest external knowledge into
|
|
669
|
+
userMessage: 'Ingest external knowledge into ktx memory.',
|
|
645
670
|
assistantMessage: input.content,
|
|
646
671
|
connectionId: input.connectionId,
|
|
647
672
|
sourceType: 'external_ingest',
|
|
@@ -1,110 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KtxExpectedError, KtxQueryError, isNativeProgrammingFault } from '../../errors.js';
|
|
2
|
+
import { executeProjectReadOnlySql } from '../../context/connections/project-sql-executor.js';
|
|
3
|
+
import { FEDERATED_CONNECTION_ID, federatedConnectionListing } from '../../context/connections/federation.js';
|
|
4
|
+
import { resolveConfiguredConnection } from '../../context/connections/resolve-connection.js';
|
|
5
|
+
import { localConnectionInfoFromConfig, } from '../../context/connections/local-warehouse-descriptor.js';
|
|
2
6
|
import { createKtxEntityDetailsService } from '../../context/scan/entity-details.js';
|
|
3
7
|
import { createKtxDiscoverDataService } from '../../context/search/discover.js';
|
|
8
|
+
import { sqlAnalysisDialectForDriver } from '../../context/sql-analysis/dialect.js';
|
|
4
9
|
import { compileLocalSlQuery } from '../../context/sl/local-query.js';
|
|
5
10
|
import { createKtxDictionarySearchService } from '../../context/sl/dictionary-search.js';
|
|
11
|
+
import { readLocalSlSource } from '../../context/sl/local-sl.js';
|
|
12
|
+
import { assertSafeConnectionId } from '../../context/sl/source-files.js';
|
|
6
13
|
import { readLocalKnowledgePage, searchLocalKnowledgePages } from '../wiki/local-knowledge.js';
|
|
7
|
-
function dialectForDriver(driver) {
|
|
8
|
-
const normalized = (driver ?? 'postgres').toUpperCase();
|
|
9
|
-
const map = {
|
|
10
|
-
POSTGRES: 'postgres',
|
|
11
|
-
BIGQUERY: 'bigquery',
|
|
12
|
-
SNOWFLAKE: 'snowflake',
|
|
13
|
-
MYSQL: 'mysql',
|
|
14
|
-
SQLSERVER: 'tsql',
|
|
15
|
-
SQLITE: 'sqlite',
|
|
16
|
-
DUCKDB: 'duckdb',
|
|
17
|
-
CLICKHOUSE: 'clickhouse',
|
|
18
|
-
DATABRICKS: 'databricks',
|
|
19
|
-
};
|
|
20
|
-
return map[normalized] ?? 'postgres';
|
|
21
|
-
}
|
|
22
|
-
function sqlAnalysisDialectForDriver(driver) {
|
|
23
|
-
return dialectForDriver(driver);
|
|
24
|
-
}
|
|
25
|
-
function assertSafePathToken(kind, value) {
|
|
26
|
-
if (value.trim().length === 0 ||
|
|
27
|
-
value.includes('..') ||
|
|
28
|
-
value.includes('\\') ||
|
|
29
|
-
value.startsWith('/') ||
|
|
30
|
-
value.startsWith('.') ||
|
|
31
|
-
value.includes('//')) {
|
|
32
|
-
throw new Error(`Unsafe ${kind}: ${value}`);
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
function assertSafeConnectionId(connectionId) {
|
|
37
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
38
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
39
|
-
}
|
|
40
|
-
return assertSafePathToken('connection id', connectionId);
|
|
41
|
-
}
|
|
42
|
-
function assertSafeSourceName(sourceName) {
|
|
43
|
-
if (!/^[a-z0-9][a-z0-9_]*$/.test(sourceName)) {
|
|
44
|
-
throw new Error(`Unsafe semantic-layer source name: ${sourceName}`);
|
|
45
|
-
}
|
|
46
|
-
return assertSafePathToken('semantic-layer source name', sourceName);
|
|
47
|
-
}
|
|
48
|
-
async function cleanupConnector(connector) {
|
|
49
|
-
if (connector?.cleanup) {
|
|
50
|
-
await connector.cleanup();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function slPath(connectionId, sourceName) {
|
|
54
|
-
return `semantic-layer/${assertSafeConnectionId(connectionId)}/${assertSafeSourceName(sourceName)}.yaml`;
|
|
55
|
-
}
|
|
56
14
|
async function executeValidatedReadOnlySql(project, options, input, onProgress) {
|
|
57
15
|
await onProgress?.({ progress: 0, message: 'Validating SQL' });
|
|
58
|
-
const connectionId = assertSafeConnectionId(input.connectionId);
|
|
59
|
-
const connection = project.config.connections[connectionId];
|
|
60
|
-
if (!connection) {
|
|
61
|
-
throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`);
|
|
62
|
-
}
|
|
63
16
|
if (!options.sqlAnalysis) {
|
|
64
17
|
throw new Error('sql_execution requires parser-backed SQL validation.');
|
|
65
18
|
}
|
|
66
|
-
const validation = await options.sqlAnalysis.validateReadOnly(input.sql, sqlAnalysisDialectForDriver(connection.driver));
|
|
67
|
-
if (!validation.ok) {
|
|
68
|
-
throw new Error(validation.error ?? 'SQL is not read-only.');
|
|
69
|
-
}
|
|
70
19
|
const createConnector = options.localScan?.createConnector;
|
|
71
20
|
if (!createConnector) {
|
|
72
21
|
throw new Error('sql_execution requires a local scan connector factory.');
|
|
73
22
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
23
|
+
const isFederated = input.connectionId === FEDERATED_CONNECTION_ID;
|
|
24
|
+
const connectionId = isFederated ? input.connectionId : assertSafeConnectionId(input.connectionId);
|
|
25
|
+
const connection = isFederated ? undefined : resolveConfiguredConnection(project.config, connectionId);
|
|
26
|
+
const dialect = sqlAnalysisDialectForDriver(isFederated ? 'duckdb' : connection.driver);
|
|
27
|
+
const validation = await options.sqlAnalysis.validateReadOnly(input.sql, dialect);
|
|
28
|
+
if (!validation.ok) {
|
|
29
|
+
// A read-only guard rejecting the agent's SQL is an expected outcome, not a
|
|
30
|
+
// ktx fault: classify it so reportException keeps it out of Error Tracking.
|
|
31
|
+
throw new KtxQueryError(validation.error ?? 'SQL is not read-only.');
|
|
32
|
+
}
|
|
33
|
+
await onProgress?.({ progress: 0.3, message: 'Executing' });
|
|
34
|
+
const result = await executeProjectReadOnlySql({
|
|
35
|
+
project,
|
|
36
|
+
input: {
|
|
82
37
|
connectionId,
|
|
38
|
+
projectDir: project.projectDir,
|
|
39
|
+
connection,
|
|
83
40
|
sql: input.sql,
|
|
84
41
|
maxRows: input.maxRows,
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
42
|
+
},
|
|
43
|
+
createConnector,
|
|
44
|
+
runId: 'mcp-sql-execution',
|
|
45
|
+
}).catch((error) => {
|
|
46
|
+
// A warehouse/driver rejection (e.g. the agent's SQL failed to compile) is a
|
|
47
|
+
// surfaced operational outcome, not a ktx fault: mark it expected while
|
|
48
|
+
// preserving the warehouse's own diagnostics. A native JS error (TypeError,
|
|
49
|
+
// etc.) signals a bug in connector code — let it propagate unchanged so Error
|
|
50
|
+
// Tracking still sees it.
|
|
51
|
+
if (isNativeProgrammingFault(error) || error instanceof KtxExpectedError) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
55
|
+
});
|
|
56
|
+
const response = {
|
|
57
|
+
headers: result.headers,
|
|
58
|
+
...(result.headerTypes ? { headerTypes: result.headerTypes } : {}),
|
|
59
|
+
rows: result.rows,
|
|
60
|
+
rowCount: result.rowCount ?? result.rows.length,
|
|
61
|
+
};
|
|
62
|
+
await onProgress?.({ progress: 1, message: `Fetched ${response.rowCount} rows` });
|
|
63
|
+
return response;
|
|
98
64
|
}
|
|
99
65
|
export function createLocalProjectMcpContextPorts(project, options) {
|
|
100
66
|
const embeddingService = options.embeddingService;
|
|
101
67
|
const ports = {
|
|
102
68
|
connections: {
|
|
103
69
|
async list() {
|
|
104
|
-
|
|
70
|
+
const configured = Object.entries(project.config.connections)
|
|
105
71
|
.map(([id, config]) => localConnectionInfoFromConfig(id, config))
|
|
106
72
|
.filter((connection) => connection !== null)
|
|
107
73
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
74
|
+
const federated = federatedConnectionListing(project.config.connections, project.projectDir);
|
|
75
|
+
if (federated) {
|
|
76
|
+
configured.push({
|
|
77
|
+
id: federated.id,
|
|
78
|
+
name: federated.id,
|
|
79
|
+
connectionType: 'DUCKDB',
|
|
80
|
+
members: federated.members,
|
|
81
|
+
hint: federated.hint,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return configured;
|
|
108
85
|
},
|
|
109
86
|
},
|
|
110
87
|
knowledge: {
|
|
@@ -148,14 +125,11 @@ export function createLocalProjectMcpContextPorts(project, options) {
|
|
|
148
125
|
},
|
|
149
126
|
semanticLayer: {
|
|
150
127
|
async readSource(input) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
128
|
+
const source = await readLocalSlSource(project, {
|
|
129
|
+
connectionId: input.connectionId,
|
|
130
|
+
sourceName: input.sourceName,
|
|
131
|
+
});
|
|
132
|
+
return source ? { sourceName: source.name, yaml: source.yaml } : null;
|
|
159
133
|
},
|
|
160
134
|
async query(input, executionOptions) {
|
|
161
135
|
if (!options.semanticLayerCompute) {
|
|
@@ -31,7 +31,7 @@ import { MemoryAgentService } from './memory-agent.service.js';
|
|
|
31
31
|
import { MemoryIngestService } from './memory-runs.js';
|
|
32
32
|
const promptsDir = fileURLToPath(new URL('../../prompts', import.meta.url));
|
|
33
33
|
const skillsDir = fileURLToPath(new URL('../../skills', import.meta.url));
|
|
34
|
-
const LOCAL_AUTHOR = { name: '
|
|
34
|
+
const LOCAL_AUTHOR = { name: 'ktx Local', email: 'local@ktx.local' };
|
|
35
35
|
const LOCAL_SHAPE_WARNING = 'Local memory ingest validates semantic-layer YAML shape only.';
|
|
36
36
|
export function createLocalProjectMemoryIngest(project, options = {}) {
|
|
37
37
|
const logger = options.logger ?? noopLogger;
|
|
@@ -307,6 +307,9 @@ class LocalShapeOnlySlValidator {
|
|
|
307
307
|
async validateSingleSource(deps, connectionId, sourceName) {
|
|
308
308
|
try {
|
|
309
309
|
const file = await deps.semanticLayerService.readSourceFile(connectionId, sourceName);
|
|
310
|
+
if (!file) {
|
|
311
|
+
return { errors: [`${sourceName}: no standalone or overlay file found`], warnings: [] };
|
|
312
|
+
}
|
|
310
313
|
const parsed = YAML.parse(file.content);
|
|
311
314
|
const isOverlay = parsed.table == null && parsed.sql == null;
|
|
312
315
|
const result = (isOverlay ? sourceOverlaySchema : sourceDefinitionSchema).safeParse(parsed);
|
|
@@ -391,7 +391,7 @@ export class MemoryAgentService {
|
|
|
391
391
|
if (session.connectionId) {
|
|
392
392
|
for (const { connectionId, sourceName } of listTouchedSlSources(session.touchedSlSources)) {
|
|
393
393
|
try {
|
|
394
|
-
const file = await this.deps.semanticLayerService.readSourceFile(connectionId, sourceName)
|
|
394
|
+
const file = await this.deps.semanticLayerService.readSourceFile(connectionId, sourceName);
|
|
395
395
|
if (file?.content) {
|
|
396
396
|
const parsed = this.parseYamlOrNull(file.content);
|
|
397
397
|
if (parsed) {
|
|
@@ -317,7 +317,6 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
317
317
|
"postgres-hybrid": "postgres-hybrid";
|
|
318
318
|
}>>;
|
|
319
319
|
git: z.ZodPrefault<z.ZodObject<{
|
|
320
|
-
auto_commit: z.ZodDefault<z.ZodBoolean>;
|
|
321
320
|
author: z.ZodDefault<z.ZodString>;
|
|
322
321
|
}, z.core.$strict>>;
|
|
323
322
|
}, z.core.$strict>>;
|
|
@@ -418,9 +417,6 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
418
417
|
default_toolset: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
419
418
|
}, z.core.$strict>>;
|
|
420
419
|
}, z.core.$strict>>;
|
|
421
|
-
memory: z.ZodPrefault<z.ZodObject<{
|
|
422
|
-
auto_commit: z.ZodDefault<z.ZodBoolean>;
|
|
423
|
-
}, z.core.$strict>>;
|
|
424
420
|
scan: z.ZodPrefault<z.ZodObject<{
|
|
425
421
|
enrichment: z.ZodPrefault<z.ZodObject<{
|
|
426
422
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -472,12 +468,23 @@ export interface KtxConfigIssue {
|
|
|
472
468
|
path: string;
|
|
473
469
|
message: string;
|
|
474
470
|
fix?: string;
|
|
471
|
+
/**
|
|
472
|
+
* 'error' blocks the project (bad value on a recognized field); 'warning' is
|
|
473
|
+
* a condition the loader recovers from on its own (an ignored unknown key).
|
|
474
|
+
*/
|
|
475
|
+
severity: 'error' | 'warning';
|
|
475
476
|
}
|
|
476
477
|
export interface KtxConfigValidation {
|
|
477
478
|
ok: boolean;
|
|
478
479
|
issues: KtxConfigIssue[];
|
|
479
480
|
}
|
|
480
481
|
export declare function buildDefaultKtxProjectConfig(): KtxProjectConfig;
|
|
482
|
+
/**
|
|
483
|
+
* Parse and validate a ktx.yaml document. Keys this ktx version does not
|
|
484
|
+
* recognize are stripped from the returned config — never from the file, which
|
|
485
|
+
* a load must not rewrite — so a config written by a different ktx version
|
|
486
|
+
* still loads. Malformed values on recognized fields still throw.
|
|
487
|
+
*/
|
|
481
488
|
export declare function parseKtxProjectConfig(raw: string): KtxProjectConfig;
|
|
482
489
|
export declare function validateKtxProjectConfig(raw: string): KtxConfigValidation;
|
|
483
490
|
export declare function generateKtxProjectConfigJsonSchema(): Record<string, unknown>;
|