@kaelio/ktx 0.13.1 → 0.14.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.1-py3-none-any.whl → kaelio_ktx-0.14.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +1 -1
- package/dist/commands/ingest-commands.d.ts +9 -0
- package/dist/commands/ingest-commands.js +37 -1
- package/dist/commands/knowledge-commands.js +3 -0
- package/dist/commands/setup-commands.js +14 -1
- package/dist/connection-drivers.d.ts +2 -0
- package/dist/connection-drivers.js +7 -3
- package/dist/connection.d.ts +3 -0
- package/dist/connection.js +27 -0
- package/dist/connectors/bigquery/connector.d.ts +17 -6
- package/dist/connectors/bigquery/connector.js +152 -76
- package/dist/connectors/bigquery/dialect.d.ts +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +1 -0
- package/dist/connectors/clickhouse/connector.js +45 -16
- package/dist/connectors/clickhouse/dialect.d.ts +2 -2
- package/dist/connectors/mongodb/connector.d.ts +69 -0
- package/dist/connectors/mongodb/connector.js +307 -0
- package/dist/connectors/mongodb/dialect.d.ts +17 -0
- package/dist/connectors/mongodb/dialect.js +50 -0
- package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
- package/dist/connectors/mongodb/live-database-introspection.js +24 -0
- package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
- package/dist/connectors/mongodb/schema-inference.js +110 -0
- package/dist/connectors/mysql/connector.d.ts +1 -0
- package/dist/connectors/mysql/connector.js +18 -2
- package/dist/connectors/mysql/dialect.d.ts +2 -2
- package/dist/connectors/postgres/connector.d.ts +1 -0
- package/dist/connectors/postgres/connector.js +20 -3
- package/dist/connectors/postgres/dialect.d.ts +2 -2
- package/dist/connectors/snowflake/connector.d.ts +1 -0
- package/dist/connectors/snowflake/connector.js +27 -3
- package/dist/connectors/snowflake/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/connector.d.ts +11 -1
- package/dist/connectors/sqlite/connector.js +120 -17
- package/dist/connectors/sqlite/dialect.d.ts +2 -2
- package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
- package/dist/connectors/sqlite/read-query-child.js +23 -0
- package/dist/connectors/sqlserver/connector.d.ts +2 -0
- package/dist/connectors/sqlserver/connector.js +20 -3
- package/dist/connectors/sqlserver/dialect.d.ts +2 -2
- package/dist/context/cache/content-result-cache.d.ts +36 -0
- package/dist/context/cache/content-result-cache.js +14 -0
- package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
- package/dist/context/cache/sqlite-content-result-cache.js +223 -0
- package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
- package/dist/context/connections/bigquery-identifiers.js +7 -0
- package/dist/context/connections/configured-connections.d.ts +9 -0
- package/dist/context/connections/configured-connections.js +18 -0
- package/dist/context/connections/dialects.d.ts +30 -4
- package/dist/context/connections/dialects.js +38 -11
- package/dist/context/connections/drivers.js +21 -0
- package/dist/context/connections/gdrive-config.d.ts +19 -0
- package/dist/context/connections/gdrive-config.js +57 -0
- package/dist/context/connections/query-deadline.d.ts +31 -0
- package/dist/context/connections/query-deadline.js +37 -0
- package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
- package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
- package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
- package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
- package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
- package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
- package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
- package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
- package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
- package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
- package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
- package/dist/context/ingest/adapters/gdrive/types.js +36 -0
- package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
- package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
- package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
- package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
- package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
- package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
- package/dist/context/ingest/adapters/live-database/stage.js +5 -5
- package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/types.js +1 -1
- package/dist/context/ingest/artifact-gates.d.ts +32 -1
- package/dist/context/ingest/artifact-gates.js +85 -18
- package/dist/context/ingest/final-gate-prune.d.ts +35 -0
- package/dist/context/ingest/final-gate-prune.js +229 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +459 -240
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
- package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
- package/dist/context/ingest/local-adapters.js +6 -0
- package/dist/context/ingest/local-bundle-runtime.js +7 -2
- package/dist/context/ingest/local-ingest.js +1 -1
- package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
- package/dist/context/ingest/local-stage-ingest.js +3 -1
- package/dist/context/ingest/ports.d.ts +3 -0
- package/dist/context/ingest/report-snapshot.js +13 -3
- package/dist/context/ingest/reports.d.ts +3 -3
- package/dist/context/ingest/reports.js +3 -1
- package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
- package/dist/context/ingest/stages/build-wu-context.js +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
- package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
- package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
- package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
- package/dist/context/ingest/types.d.ts +3 -0
- package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
- package/dist/context/ingest/wiki-body-refs.js +37 -8
- package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
- package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
- package/dist/context/ingest/work-unit-cache.d.ts +67 -0
- package/dist/context/ingest/work-unit-cache.js +230 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.js +5 -0
- package/dist/context/llm/claude-code-runtime.d.ts +4 -1
- package/dist/context/llm/claude-code-runtime.js +38 -8
- package/dist/context/llm/codex-runtime.d.ts +4 -1
- package/dist/context/llm/codex-runtime.js +48 -35
- package/dist/context/llm/runtime-port.d.ts +26 -0
- package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
- package/dist/context/llm/subprocess-generate-object-child.js +34 -0
- package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
- package/dist/context/llm/subprocess-generate-object.js +122 -0
- package/dist/context/mcp/context-tools.d.ts +2 -0
- package/dist/context/mcp/context-tools.js +111 -17
- package/dist/context/mcp/local-project-ports.d.ts +7 -0
- package/dist/context/mcp/local-project-ports.js +29 -0
- package/dist/context/mcp/logger.d.ts +24 -0
- package/dist/context/mcp/logger.js +49 -0
- package/dist/context/mcp/server.js +2 -0
- package/dist/context/mcp/types.d.ts +17 -0
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +42 -0
- package/dist/context/project/config.js +5 -0
- package/dist/context/project/driver-schemas.d.ts +20 -0
- package/dist/context/project/driver-schemas.js +50 -1
- package/dist/context/project/project.js +1 -1
- package/dist/context/project/setup-config.js +1 -0
- package/dist/context/scan/description-generation.d.ts +4 -0
- package/dist/context/scan/description-generation.js +100 -13
- package/dist/context/scan/enabled-tables.d.ts +5 -0
- package/dist/context/scan/enabled-tables.js +13 -1
- package/dist/context/scan/enrichment-state.d.ts +50 -7
- package/dist/context/scan/enrichment-state.js +30 -13
- package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
- package/dist/context/scan/local-enrichment-artifacts.js +155 -32
- package/dist/context/scan/local-enrichment.d.ts +39 -4
- package/dist/context/scan/local-enrichment.js +345 -92
- package/dist/context/scan/local-scan.d.ts +4 -1
- package/dist/context/scan/local-scan.js +54 -13
- package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
- package/dist/context/scan/local-structural-artifacts.js +5 -0
- package/dist/context/scan/object-introspection.d.ts +21 -0
- package/dist/context/scan/object-introspection.js +35 -0
- package/dist/context/scan/relationship-benchmarks.js +5 -3
- package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
- package/dist/context/scan/relationship-composite-candidates.js +13 -1
- package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
- package/dist/context/scan/relationship-detection-budget.js +53 -0
- package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
- package/dist/context/scan/relationship-diagnostics.js +2 -0
- package/dist/context/scan/relationship-discovery.d.ts +9 -3
- package/dist/context/scan/relationship-discovery.js +27 -4
- package/dist/context/scan/relationship-llm-proposal.js +36 -27
- package/dist/context/scan/relationship-profiling.d.ts +7 -3
- package/dist/context/scan/relationship-profiling.js +53 -41
- package/dist/context/scan/relationship-validation.d.ts +6 -4
- package/dist/context/scan/relationship-validation.js +46 -32
- package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
- package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
- package/dist/context/scan/types.d.ts +5 -3
- package/dist/context/scan/types.js +2 -0
- package/dist/context/sl/local-query.js +5 -0
- package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
- package/dist/context/sl/semantic-layer.service.js +1 -1
- package/dist/context/sl/source-files.d.ts +5 -0
- package/dist/context/sl/source-files.js +17 -11
- package/dist/context/sl/tools/connection-id-schema.js +1 -1
- package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
- package/dist/context/sql-analysis/dialect-notes.js +40 -0
- package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
- package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
- package/dist/context/sql-analysis/dialects/mysql.md +9 -0
- package/dist/context/sql-analysis/dialects/postgres.md +10 -0
- package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
- package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
- package/dist/context/sql-analysis/dialects/tsql.md +10 -0
- package/dist/context/wiki/keys.js +1 -1
- package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
- package/dist/context/wiki/knowledge-wiki.service.js +2 -1
- package/dist/context/wiki/local-knowledge.d.ts +13 -0
- package/dist/context/wiki/local-knowledge.js +50 -6
- package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
- package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
- package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
- package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
- package/dist/context/wiki/types.d.ts +6 -0
- package/dist/context-build-view.d.ts +3 -0
- package/dist/context-build-view.js +1 -0
- package/dist/knowledge.d.ts +2 -0
- package/dist/knowledge.js +12 -1
- package/dist/local-adapters.js +11 -0
- package/dist/mcp-http-server.js +15 -1
- package/dist/mcp-server-factory.d.ts +2 -0
- package/dist/mcp-server-factory.js +15 -1
- package/dist/mcp-stdio-server.js +10 -2
- package/dist/notion-page-picker.js +1 -1
- package/dist/prompts/memory_agent_external_ingest.md +2 -0
- package/dist/public-ingest.d.ts +3 -1
- package/dist/public-ingest.js +3 -0
- package/dist/scan.d.ts +3 -1
- package/dist/scan.js +7 -0
- package/dist/setup-databases.d.ts +1 -1
- package/dist/setup-databases.js +43 -1
- package/dist/setup-sources.d.ts +5 -1
- package/dist/setup-sources.js +124 -48
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +6 -1
- package/dist/skills/analytics/SKILL.md +200 -2
- package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
- package/dist/skills/wiki_capture/SKILL.md +24 -0
- package/dist/sql.js +4 -0
- package/dist/status-project.d.ts +4 -0
- package/dist/status-project.js +54 -2
- package/dist/telemetry/events.d.ts +2 -2
- package/dist/text-ingest.d.ts +4 -0
- package/dist/text-ingest.js +58 -29
- package/dist/verbatim-ingest.d.ts +46 -0
- package/dist/verbatim-ingest.js +193 -0
- package/package.json +5 -1
- package/dist/context/ingest/final-gate-repair.d.ts +0 -28
- package/dist/context/ingest/final-gate-repair.js +0 -91
|
@@ -185,48 +185,61 @@ export class CodexKtxLlmRuntime {
|
|
|
185
185
|
await mcp?.close();
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
// Structured generation has no tools, so it skips the MCP server that
|
|
189
|
+
// generateText/runAgentLoop need; generateObject and generateStructuredJson
|
|
190
|
+
// (the kill-boundary child path) share this one streaming implementation.
|
|
191
|
+
async streamStructuredText(input) {
|
|
189
192
|
const startedAt = Date.now();
|
|
190
193
|
const model = modelForRole(this.deps.modelSlots, input.role);
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
const config = buildCodexRuntimeConfig({ model });
|
|
195
|
+
const result = await this.runWithRateLimitRetry(input.abortSignal, async () => {
|
|
196
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
197
|
+
projectDir: this.deps.projectDir,
|
|
198
|
+
model,
|
|
199
|
+
prompt: promptWithSystem(input.system, input.prompt),
|
|
200
|
+
configOverrides: config.configOverrides,
|
|
201
|
+
env: config.env,
|
|
202
|
+
outputSchema: input.jsonSchema,
|
|
203
|
+
...(input.abortSignal ? { signal: input.abortSignal } : {}),
|
|
204
|
+
}));
|
|
205
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
206
|
+
return { collected, summary };
|
|
207
|
+
}, ({ collected, summary }) => summaryError(summary, collected.streamError));
|
|
208
|
+
return {
|
|
209
|
+
text: assertSuccessfulText(result.summary, result.collected.streamError),
|
|
210
|
+
summary: result.summary,
|
|
211
|
+
startedAt,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async generateObject(input) {
|
|
215
|
+
const { text, summary, startedAt } = await this.streamStructuredText({
|
|
216
|
+
role: input.role,
|
|
217
|
+
prompt: input.prompt,
|
|
218
|
+
...(input.system !== undefined ? { system: input.system } : {}),
|
|
219
|
+
jsonSchema: z.toJSONSchema(input.schema, { target: 'draft-7' }),
|
|
220
|
+
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
|
221
|
+
});
|
|
222
|
+
input.onMetrics?.(metrics(summary, startedAt));
|
|
223
|
+
return parseStructuredOutput(input.schema, text);
|
|
224
|
+
}
|
|
225
|
+
async generateStructuredJson(input) {
|
|
226
|
+
const { text } = await this.streamStructuredText({
|
|
227
|
+
role: input.role,
|
|
228
|
+
prompt: input.prompt,
|
|
229
|
+
...(input.system !== undefined ? { system: input.system } : {}),
|
|
230
|
+
jsonSchema: input.jsonSchema,
|
|
231
|
+
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
|
195
232
|
});
|
|
196
233
|
try {
|
|
197
|
-
|
|
198
|
-
model,
|
|
199
|
-
...(mcp
|
|
200
|
-
? {
|
|
201
|
-
mcp: {
|
|
202
|
-
url: mcp.url,
|
|
203
|
-
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
204
|
-
bearerToken: mcp.bearerToken,
|
|
205
|
-
toolNames: runtimeToolNames(input.tools),
|
|
206
|
-
},
|
|
207
|
-
}
|
|
208
|
-
: {}),
|
|
209
|
-
});
|
|
210
|
-
const result = await this.runWithRateLimitRetry(input.abortSignal, async () => {
|
|
211
|
-
const collected = await collectEvents(await this.runner.runStreamed({
|
|
212
|
-
projectDir: this.deps.projectDir,
|
|
213
|
-
model,
|
|
214
|
-
prompt: promptWithSystem(input.system, input.prompt),
|
|
215
|
-
configOverrides: config.configOverrides,
|
|
216
|
-
env: config.env,
|
|
217
|
-
outputSchema: z.toJSONSchema(input.schema, { target: 'draft-7' }),
|
|
218
|
-
...(input.abortSignal ? { signal: input.abortSignal } : {}),
|
|
219
|
-
}));
|
|
220
|
-
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
221
|
-
return { collected, summary };
|
|
222
|
-
}, ({ collected, summary }) => summaryError(summary, collected.streamError));
|
|
223
|
-
input.onMetrics?.(metrics(result.summary, startedAt));
|
|
224
|
-
return parseStructuredOutput(input.schema, assertSuccessfulText(result.summary, result.collected.streamError));
|
|
234
|
+
return JSON.parse(text);
|
|
225
235
|
}
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
catch (error) {
|
|
237
|
+
throw new Error(`Codex structured output is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
228
238
|
}
|
|
229
239
|
}
|
|
240
|
+
subprocessForkSpec() {
|
|
241
|
+
return { backend: 'codex', projectDir: this.deps.projectDir, modelSlots: this.deps.modelSlots };
|
|
242
|
+
}
|
|
230
243
|
async runAgentLoop(params) {
|
|
231
244
|
const startedAt = Date.now();
|
|
232
245
|
const model = modelForRole(this.deps.modelSlots, params.modelRole);
|
|
@@ -67,10 +67,36 @@ export interface KtxGenerateObjectInput<TOutput, TSchema extends z.ZodType<TOutp
|
|
|
67
67
|
}) => void;
|
|
68
68
|
abortSignal?: AbortSignal;
|
|
69
69
|
}
|
|
70
|
+
/** Structured generation keyed by a raw JSON Schema instead of a Zod schema, so
|
|
71
|
+
* the request can cross a process boundary; the caller validates the returned
|
|
72
|
+
* value against the real Zod schema. */
|
|
73
|
+
export interface KtxGenerateStructuredJsonInput {
|
|
74
|
+
role: KtxModelRole;
|
|
75
|
+
prompt: string;
|
|
76
|
+
system?: string;
|
|
77
|
+
jsonSchema: Record<string, unknown>;
|
|
78
|
+
abortSignal?: AbortSignal;
|
|
79
|
+
}
|
|
80
|
+
/** Serializable recipe to rebuild a subprocess-backed runtime inside a ktx-owned
|
|
81
|
+
* child the parent can tree-kill. Returned by {@link KtxLlmRuntimePort.subprocessForkSpec}. */
|
|
82
|
+
export interface SubprocessRuntimeForkSpec {
|
|
83
|
+
backend: 'codex' | 'claude-code';
|
|
84
|
+
projectDir: string;
|
|
85
|
+
modelSlots: {
|
|
86
|
+
default: string;
|
|
87
|
+
} & Partial<Record<string, string>>;
|
|
88
|
+
}
|
|
70
89
|
export interface KtxLlmRuntimePort {
|
|
71
90
|
generateText(input: KtxGenerateTextInput): Promise<string>;
|
|
72
91
|
generateObject<TOutput, TSchema extends z.ZodType<TOutput>>(input: KtxGenerateObjectInput<TOutput, TSchema>): Promise<TOutput>;
|
|
73
92
|
runAgentLoop(params: RunLoopParams): Promise<RunLoopResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Non-null when this runtime drives an SDK-owned child process that ktx cannot
|
|
95
|
+
* cancel by abort alone (codex/claude-code spawn a binary the SDK owns and only
|
|
96
|
+
* SIGTERM on abort). ktx routes such calls through a tree-killable boundary.
|
|
97
|
+
* Null for HTTP backends, whose native fetch abort already settles promptly.
|
|
98
|
+
*/
|
|
99
|
+
subprocessForkSpec(): SubprocessRuntimeForkSpec | null;
|
|
74
100
|
}
|
|
75
101
|
export interface AgentRunnerPort {
|
|
76
102
|
runLoop(params: RunLoopParams): Promise<RunLoopResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ClaudeCodeKtxLlmRuntime } from './claude-code-runtime.js';
|
|
2
|
+
import { CodexKtxLlmRuntime } from './codex-runtime.js';
|
|
3
|
+
// Forked by the parent as a process-group leader it can SIGKILL as a tree. Hosts
|
|
4
|
+
// one structured LLM call for a subprocess-backed runtime (codex/claude-code);
|
|
5
|
+
// the SDK spawns the model binary as this process's own child, so a parent
|
|
6
|
+
// tree-kill reaps the wedged model too. Credentials flow via inherited env — the
|
|
7
|
+
// runtimes re-derive their allowlisted env from process.env — never over IPC.
|
|
8
|
+
function buildRuntime(forkSpec) {
|
|
9
|
+
if (forkSpec.backend === 'codex') {
|
|
10
|
+
return new CodexKtxLlmRuntime({ projectDir: forkSpec.projectDir, modelSlots: forkSpec.modelSlots });
|
|
11
|
+
}
|
|
12
|
+
return new ClaudeCodeKtxLlmRuntime({ projectDir: forkSpec.projectDir, modelSlots: forkSpec.modelSlots });
|
|
13
|
+
}
|
|
14
|
+
// The parent owns this process's lifecycle. If the parent dies its IPC channel
|
|
15
|
+
// drops; exit rather than linger as an orphan holding a provider connection.
|
|
16
|
+
process.once('disconnect', () => process.exit(0));
|
|
17
|
+
process.once('message', (request) => {
|
|
18
|
+
void (async () => {
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
const output = await buildRuntime(request.forkSpec).generateStructuredJson({
|
|
22
|
+
role: request.role,
|
|
23
|
+
prompt: request.prompt,
|
|
24
|
+
...(request.system !== undefined ? { system: request.system } : {}),
|
|
25
|
+
jsonSchema: request.jsonSchema,
|
|
26
|
+
});
|
|
27
|
+
response = { ok: true, output };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
response = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
31
|
+
}
|
|
32
|
+
process.send?.(response, () => process.exit(0));
|
|
33
|
+
})();
|
|
34
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { KtxModelRole } from '../../llm/types.js';
|
|
4
|
+
import type { SubprocessRuntimeForkSpec } from './runtime-port.js';
|
|
5
|
+
export interface SubprocessGenerateObjectRequest {
|
|
6
|
+
forkSpec: SubprocessRuntimeForkSpec;
|
|
7
|
+
role: KtxModelRole;
|
|
8
|
+
prompt: string;
|
|
9
|
+
system?: string;
|
|
10
|
+
jsonSchema: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export type SubprocessGenerateObjectResponse = {
|
|
13
|
+
ok: true;
|
|
14
|
+
output: unknown;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
/** A per-table enrichment subprocess that did not return before its deadline. */
|
|
20
|
+
export declare class KtxSubprocessDeadlineError extends Error {
|
|
21
|
+
readonly deadlineMs: number;
|
|
22
|
+
constructor(deadlineMs: number);
|
|
23
|
+
}
|
|
24
|
+
export interface RunGenerateObjectInSubprocessInput<TOutput, TSchema extends z.ZodType<TOutput>> {
|
|
25
|
+
forkSpec: SubprocessRuntimeForkSpec;
|
|
26
|
+
role: KtxModelRole;
|
|
27
|
+
prompt: string;
|
|
28
|
+
system?: string;
|
|
29
|
+
schema: TSchema;
|
|
30
|
+
jsonSchema: Record<string, unknown>;
|
|
31
|
+
deadlineMs: number;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
/** @internal Test seam: spawn the child so tests can observe its lifecycle. */
|
|
34
|
+
spawnChild?: () => ChildProcess;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run one structured LLM call for a subprocess-backed runtime behind a boundary
|
|
38
|
+
* ktx can hard-kill. On the deadline or an external abort, the whole process
|
|
39
|
+
* group/tree is SIGKILLed (reaping the SDK's wedged model child) and the promise
|
|
40
|
+
* settles promptly; on success the raw output is validated against the Zod schema.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runGenerateObjectInSubprocess<TOutput, TSchema extends z.ZodType<TOutput>>(input: RunGenerateObjectInSubprocessInput<TOutput, TSchema>): Promise<TOutput>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { fork, spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { createAbortError } from '../core/abort.js';
|
|
5
|
+
// In dist, this file and the child are siblings; under vitest the compiled .js is
|
|
6
|
+
// absent and Node strips types from the .ts. The real child imports the codex /
|
|
7
|
+
// claude SDKs (which use constructor parameter properties), so it only runs as
|
|
8
|
+
// built .js — tests inject a fake child via the spawnChild seam.
|
|
9
|
+
function childUrl() {
|
|
10
|
+
const builtChild = new URL('./subprocess-generate-object-child.js', import.meta.url);
|
|
11
|
+
return existsSync(fileURLToPath(builtChild)) ? builtChild : new URL('./subprocess-generate-object-child.ts', import.meta.url);
|
|
12
|
+
}
|
|
13
|
+
function forkSubprocessGenerateObjectChild() {
|
|
14
|
+
// detached: the child becomes a process-group leader so the SDK's grandchild
|
|
15
|
+
// (the codex/claude binary) inherits its group and a negative-pid SIGKILL reaps
|
|
16
|
+
// the whole tree. Empty execArgv keeps it a clean Node process.
|
|
17
|
+
return fork(childUrl(), {
|
|
18
|
+
execArgv: [],
|
|
19
|
+
serialization: 'advanced',
|
|
20
|
+
detached: true,
|
|
21
|
+
stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** A per-table enrichment subprocess that did not return before its deadline. */
|
|
25
|
+
export class KtxSubprocessDeadlineError extends Error {
|
|
26
|
+
deadlineMs;
|
|
27
|
+
constructor(deadlineMs) {
|
|
28
|
+
super(`enrichment subprocess exceeded ${Math.round(deadlineMs / 1000)}s`);
|
|
29
|
+
this.deadlineMs = deadlineMs;
|
|
30
|
+
this.name = 'KtxSubprocessDeadlineError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// SIGTERM is too gentle for a child wedged on a hung provider socket; the SDK
|
|
34
|
+
// grandchild ignores it and survives. Kill the whole tree: the detached process
|
|
35
|
+
// group on POSIX, the process tree via taskkill /T on Windows.
|
|
36
|
+
function killProcessTree(child) {
|
|
37
|
+
if (child.pid === undefined) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' }).on('error', () => undefined);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
try {
|
|
49
|
+
child.kill('SIGKILL');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Already exited.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Run one structured LLM call for a subprocess-backed runtime behind a boundary
|
|
58
|
+
* ktx can hard-kill. On the deadline or an external abort, the whole process
|
|
59
|
+
* group/tree is SIGKILLed (reaping the SDK's wedged model child) and the promise
|
|
60
|
+
* settles promptly; on success the raw output is validated against the Zod schema.
|
|
61
|
+
*/
|
|
62
|
+
export function runGenerateObjectInSubprocess(input) {
|
|
63
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
64
|
+
const child = (input.spawnChild ?? forkSubprocessGenerateObjectChild)();
|
|
65
|
+
let settled = false;
|
|
66
|
+
const onDeadline = () => settle(() => rejectPromise(new KtxSubprocessDeadlineError(input.deadlineMs)));
|
|
67
|
+
const onAbort = () => settle(() => rejectPromise(createAbortError()));
|
|
68
|
+
const timer = setTimeout(onDeadline, input.deadlineMs);
|
|
69
|
+
function settle(finish) {
|
|
70
|
+
if (settled) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
settled = true;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
input.signal?.removeEventListener('abort', onAbort);
|
|
76
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
77
|
+
killProcessTree(child);
|
|
78
|
+
}
|
|
79
|
+
finish();
|
|
80
|
+
}
|
|
81
|
+
child.on('message', (message) => {
|
|
82
|
+
if (message.ok) {
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = input.schema.parse(message.output);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
settle(() => rejectPromise(error instanceof Error ? error : new Error(String(error))));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
settle(() => resolvePromise(parsed));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
settle(() => rejectPromise(new Error(message.message)));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
child.on('error', (error) => settle(() => rejectPromise(error)));
|
|
98
|
+
child.on('exit', (code, processSignal) => {
|
|
99
|
+
if (!settled) {
|
|
100
|
+
settle(() => rejectPromise(new Error(`enrichment subprocess exited before returning a result (code ${code}, signal ${processSignal}).`)));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (input.signal?.aborted) {
|
|
104
|
+
onAbort();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
input.signal?.addEventListener('abort', onAbort, { once: true });
|
|
108
|
+
try {
|
|
109
|
+
const request = {
|
|
110
|
+
forkSpec: input.forkSpec,
|
|
111
|
+
role: input.role,
|
|
112
|
+
prompt: input.prompt,
|
|
113
|
+
...(input.system !== undefined ? { system: input.system } : {}),
|
|
114
|
+
jsonSchema: input.jsonSchema,
|
|
115
|
+
};
|
|
116
|
+
child.send(request);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
settle(() => rejectPromise(error instanceof Error ? error : new Error(String(error))));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KtxCliIo } from '../../cli-runtime.js';
|
|
2
|
+
import { type KtxMcpLogger } from './logger.js';
|
|
2
3
|
import type { KtxMcpClientInfo, KtxMcpContextPorts, KtxMcpServerLike, KtxMcpToolResult, KtxMcpUserContext, NonArrayObject } from './types.js';
|
|
3
4
|
export interface RegisterKtxContextToolsDeps {
|
|
4
5
|
server: KtxMcpServerLike;
|
|
@@ -6,6 +7,7 @@ export interface RegisterKtxContextToolsDeps {
|
|
|
6
7
|
userContext: KtxMcpUserContext;
|
|
7
8
|
projectDir?: string;
|
|
8
9
|
io?: KtxCliIo;
|
|
10
|
+
logger?: KtxMcpLogger;
|
|
9
11
|
getClientInfo?: () => KtxMcpClientInfo | undefined;
|
|
10
12
|
}
|
|
11
13
|
/** @internal */
|
|
@@ -3,6 +3,7 @@ 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
5
|
import { formatErrorDetail, scrubErrorClass } from '../../telemetry/scrubber.js';
|
|
6
|
+
import { mcpSlowToolMs, serializeMcpError } from './logger.js';
|
|
6
7
|
const connectionIdSchema = z.string().min(1);
|
|
7
8
|
const unknownRecordSchema = z.record(z.string(), z.unknown());
|
|
8
9
|
const tableRefSchema = z.object({
|
|
@@ -20,19 +21,21 @@ const toolAnnotations = {
|
|
|
20
21
|
sl_read_source: { title: 'Semantic Layer Read Source', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
21
22
|
sl_query: { title: 'Semantic Layer Query', readOnlyHint: true, openWorldHint: false },
|
|
22
23
|
sql_execution: { title: 'SQL Execution', readOnlyHint: true, openWorldHint: false },
|
|
24
|
+
sql_dialect_notes: { title: 'SQL Dialect Notes', readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
23
25
|
memory_ingest: { title: 'Memory Ingest', destructiveHint: true, openWorldHint: false },
|
|
24
26
|
memory_ingest_status: { title: 'Memory Ingest Status', readOnlyHint: true, openWorldHint: false },
|
|
25
27
|
};
|
|
26
28
|
const toolDescriptions = {
|
|
27
29
|
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
30
|
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 }).',
|
|
31
|
+
wiki_search: 'Search ktx wiki pages for reusable business context. Pass connectionId to scope results to one warehouse (unscoped pages plus pages tagged with that connection) when a concept name collides across databases. Example: wiki_search({ query: "revenue recognition", connectionId: "warehouse", limit: 5 }).',
|
|
30
32
|
wiki_read: 'Read a ktx wiki page by key returned from wiki_search. Example: wiki_read({ key: "global/revenue" }).',
|
|
31
33
|
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
34
|
dictionary_search: 'Search profile-sampled warehouse values to locate likely source columns for business values. Example: dictionary_search({ values: ["Acme Corp"], connectionId: "warehouse" }).',
|
|
33
35
|
sl_read_source: 'Read a semantic-layer YAML source by connection id and source name. Example: sl_read_source({ connectionId: "warehouse", sourceName: "orders" }).',
|
|
34
36
|
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
37
|
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 }).',
|
|
38
|
+
sql_dialect_notes: 'Return the SQL syntax conventions for the dialect of a ktx connection: fully-qualified table-name form, identifier quoting and case-folding, date/time functions, top-N / window-filtering idiom, and JSON access. Call this before writing raw sql_execution SQL against a connection so the SQL matches that engine. Example: sql_dialect_notes({ connectionId: "warehouse" }).',
|
|
36
39
|
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
40
|
memory_ingest_status: 'Read the current or final status for a memory ingest run. Example: memory_ingest_status({ runId: "memory-run-1" }).',
|
|
38
41
|
};
|
|
@@ -40,6 +43,9 @@ const connectionListSchema = z.object({});
|
|
|
40
43
|
const knowledgeSearchSchema = z.object({
|
|
41
44
|
query: z.string().min(1).describe('Natural-language wiki search query, e.g. "revenue recognition policy".'),
|
|
42
45
|
limit: z.number().int().min(1).max(50).default(10).describe('Maximum wiki pages to return.'),
|
|
46
|
+
connectionId: connectionIdSchema
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Scope results to one connection: returns unscoped pages plus pages tagged with this connection. Omit to search all pages.'),
|
|
43
49
|
});
|
|
44
50
|
const knowledgeReadSchema = z.object({
|
|
45
51
|
key: z.string().min(1).describe('Wiki page key returned by wiki_search, e.g. "global/revenue".'),
|
|
@@ -143,6 +149,9 @@ const sqlExecutionSchema = z.object({
|
|
|
143
149
|
sql: z.string().min(1).describe('Parser-validated read-only SQL, e.g. "select count(*) from public.orders".'),
|
|
144
150
|
maxRows: z.number().int().min(1).max(10_000).default(1000).optional().describe('Maximum rows to return.'),
|
|
145
151
|
});
|
|
152
|
+
const sqlDialectNotesSchema = z.object({
|
|
153
|
+
connectionId: connectionIdSchema.describe('Connection id whose engine dialect conventions to return.'),
|
|
154
|
+
});
|
|
146
155
|
const memoryIngestSchema = z.object({
|
|
147
156
|
content: z
|
|
148
157
|
.string()
|
|
@@ -309,6 +318,11 @@ const sqlExecutionOutputSchema = z.object({
|
|
|
309
318
|
rows: z.array(z.array(z.unknown())),
|
|
310
319
|
rowCount: z.number(),
|
|
311
320
|
});
|
|
321
|
+
const sqlDialectNotesOutputSchema = z.object({
|
|
322
|
+
connectionId: z.string(),
|
|
323
|
+
dialect: z.string(),
|
|
324
|
+
notes: z.string(),
|
|
325
|
+
});
|
|
312
326
|
const memoryIngestOutputSchema = z.object({
|
|
313
327
|
runId: z.string(),
|
|
314
328
|
});
|
|
@@ -444,6 +458,47 @@ function clientTelemetryFields(getClientInfo) {
|
|
|
444
458
|
...(client?.version ? { mcpClientVersion: client.version } : {}),
|
|
445
459
|
};
|
|
446
460
|
}
|
|
461
|
+
function toolResultIsError(result) {
|
|
462
|
+
return (typeof result === 'object' && result !== null && 'isError' in result && result.isError === true);
|
|
463
|
+
}
|
|
464
|
+
/** Tool-agnostic size: byte length of the serialized text content the client reads. */
|
|
465
|
+
function toolResultSize(result) {
|
|
466
|
+
if (typeof result !== 'object' || result === null || !('content' in result)) {
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
const content = result.content;
|
|
470
|
+
if (!Array.isArray(content)) {
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
let size = 0;
|
|
474
|
+
for (const item of content) {
|
|
475
|
+
if (item && typeof item === 'object' && item.type === 'text') {
|
|
476
|
+
const text = item.text;
|
|
477
|
+
if (typeof text === 'string') {
|
|
478
|
+
size += Buffer.byteLength(text, 'utf8');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return size;
|
|
483
|
+
}
|
|
484
|
+
function toolResultErrorText(result) {
|
|
485
|
+
if (typeof result === 'object' && result !== null && 'content' in result) {
|
|
486
|
+
const content = result.content;
|
|
487
|
+
if (Array.isArray(content)) {
|
|
488
|
+
const text = content
|
|
489
|
+
.filter((item) => !!item &&
|
|
490
|
+
typeof item === 'object' &&
|
|
491
|
+
item.type === 'text' &&
|
|
492
|
+
typeof item.text === 'string')
|
|
493
|
+
.map((item) => item.text)
|
|
494
|
+
.join('\n');
|
|
495
|
+
if (text.length > 0) {
|
|
496
|
+
return text;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return 'Tool returned an error result.';
|
|
501
|
+
}
|
|
447
502
|
// Tools registered via registerParsedTool catch their own errors and return an
|
|
448
503
|
// isError result, so the telemetry layer never sees the thrown Error. Recover
|
|
449
504
|
// the failure message from the result's text content (the same string the agent
|
|
@@ -463,65 +518,91 @@ function mcpErrorResultDetail(result) {
|
|
|
463
518
|
.join('\n');
|
|
464
519
|
return formatErrorDetail(text);
|
|
465
520
|
}
|
|
466
|
-
function instrumentMcpServer(server,
|
|
521
|
+
function instrumentMcpServer(server, deps) {
|
|
467
522
|
return {
|
|
468
523
|
registerTool(name, config, handler) {
|
|
469
524
|
server.registerTool(name, config, async (input, context) => {
|
|
525
|
+
const callId = randomUUID();
|
|
526
|
+
const callLogger = deps.logger?.child({
|
|
527
|
+
tool: name,
|
|
528
|
+
callId,
|
|
529
|
+
...(context?.sessionId ? { sessionId: context.sessionId } : {}),
|
|
530
|
+
});
|
|
470
531
|
const startedAt = performance.now();
|
|
532
|
+
// Synchronous, before the (possibly blocking) handler: a runaway query that never
|
|
533
|
+
// returns still leaves this start line — with its exact params — on disk.
|
|
534
|
+
callLogger?.info({ params: input }, 'tool.start');
|
|
471
535
|
try {
|
|
472
536
|
const result = await handler(input, context);
|
|
473
|
-
|
|
474
|
-
|
|
537
|
+
const durationMs = Math.max(0, performance.now() - startedAt);
|
|
538
|
+
const isError = toolResultIsError(result);
|
|
539
|
+
if (deps.io && deps.projectDir && shouldEmitMcpTelemetry()) {
|
|
475
540
|
const errorDetail = isError ? mcpErrorResultDetail(result) : undefined;
|
|
476
541
|
await emitTelemetryEvent({
|
|
477
542
|
name: 'mcp_request_completed',
|
|
478
|
-
projectDir:
|
|
479
|
-
io:
|
|
543
|
+
projectDir: deps.projectDir,
|
|
544
|
+
io: deps.io,
|
|
480
545
|
fields: {
|
|
481
546
|
toolName: name,
|
|
482
547
|
outcome: isError ? 'error' : 'ok',
|
|
483
|
-
durationMs
|
|
548
|
+
durationMs,
|
|
484
549
|
sampleRate: mcpTelemetrySampleRate(),
|
|
485
550
|
...(errorDetail ? { errorDetail } : {}),
|
|
486
|
-
...clientTelemetryFields(
|
|
551
|
+
...clientTelemetryFields(deps.getClientInfo),
|
|
487
552
|
},
|
|
488
553
|
});
|
|
489
554
|
}
|
|
555
|
+
if (callLogger) {
|
|
556
|
+
if (isError) {
|
|
557
|
+
callLogger.error({ durationMs, outcome: 'error', err: serializeMcpError(toolResultErrorText(result)) }, 'tool.end');
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
const fields = { durationMs, outcome: 'ok', resultSize: toolResultSize(result) };
|
|
561
|
+
if (durationMs > deps.slowToolMs) {
|
|
562
|
+
callLogger.warn(fields, 'tool.end');
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
callLogger.info(fields, 'tool.end');
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
490
569
|
return result;
|
|
491
570
|
}
|
|
492
571
|
catch (error) {
|
|
493
|
-
|
|
572
|
+
const durationMs = Math.max(0, performance.now() - startedAt);
|
|
573
|
+
if (deps.io) {
|
|
494
574
|
await reportException({
|
|
495
575
|
error,
|
|
496
576
|
context: { source: `mcp:${name}`, handled: true, fatal: false },
|
|
497
|
-
projectDir:
|
|
498
|
-
io:
|
|
577
|
+
projectDir: deps.projectDir,
|
|
578
|
+
io: deps.io,
|
|
499
579
|
redactionSecrets: await collectTelemetryRedactionSecrets({
|
|
500
|
-
projectDir:
|
|
580
|
+
projectDir: deps.projectDir,
|
|
501
581
|
includeLlm: true,
|
|
502
582
|
includeEmbeddings: true,
|
|
503
583
|
env: process.env,
|
|
504
584
|
}),
|
|
505
585
|
});
|
|
506
586
|
}
|
|
507
|
-
if (
|
|
587
|
+
if (deps.io && deps.projectDir && shouldEmitMcpTelemetry()) {
|
|
508
588
|
const errorClass = scrubErrorClass(error);
|
|
509
589
|
const errorDetail = formatErrorDetail(error);
|
|
510
590
|
await emitTelemetryEvent({
|
|
511
591
|
name: 'mcp_request_completed',
|
|
512
|
-
projectDir:
|
|
513
|
-
io:
|
|
592
|
+
projectDir: deps.projectDir,
|
|
593
|
+
io: deps.io,
|
|
514
594
|
fields: {
|
|
515
595
|
toolName: name,
|
|
516
596
|
outcome: 'error',
|
|
517
597
|
...(errorClass ? { errorClass } : {}),
|
|
518
598
|
...(errorDetail ? { errorDetail } : {}),
|
|
519
|
-
durationMs
|
|
599
|
+
durationMs,
|
|
520
600
|
sampleRate: mcpTelemetrySampleRate(),
|
|
521
|
-
...clientTelemetryFields(
|
|
601
|
+
...clientTelemetryFields(deps.getClientInfo),
|
|
522
602
|
},
|
|
523
603
|
});
|
|
524
604
|
}
|
|
605
|
+
callLogger?.error({ durationMs, outcome: 'error', err: serializeMcpError(error) }, 'tool.end');
|
|
525
606
|
throw error;
|
|
526
607
|
}
|
|
527
608
|
});
|
|
@@ -534,6 +615,8 @@ export function registerKtxContextTools(deps) {
|
|
|
534
615
|
const server = instrumentMcpServer(deps.server, {
|
|
535
616
|
projectDir: deps.projectDir,
|
|
536
617
|
io: deps.io,
|
|
618
|
+
logger: deps.logger,
|
|
619
|
+
slowToolMs: mcpSlowToolMs(),
|
|
537
620
|
getClientInfo: deps.getClientInfo,
|
|
538
621
|
});
|
|
539
622
|
if (ports.connections) {
|
|
@@ -558,6 +641,7 @@ export function registerKtxContextTools(deps) {
|
|
|
558
641
|
userId: userContext.userId,
|
|
559
642
|
query: input.query,
|
|
560
643
|
limit: input.limit,
|
|
644
|
+
...(input.connectionId !== undefined ? { connectionId: input.connectionId } : {}),
|
|
561
645
|
})), toolTelemetry);
|
|
562
646
|
registerParsedTool(server, 'wiki_read', {
|
|
563
647
|
title: toolAnnotations.wiki_read.title,
|
|
@@ -654,6 +738,16 @@ export function registerKtxContextTools(deps) {
|
|
|
654
738
|
}, onProgress ? { onProgress } : undefined));
|
|
655
739
|
}, toolTelemetry);
|
|
656
740
|
}
|
|
741
|
+
if (ports.dialectNotes) {
|
|
742
|
+
const dialectNotes = ports.dialectNotes;
|
|
743
|
+
registerParsedTool(server, 'sql_dialect_notes', {
|
|
744
|
+
title: toolAnnotations.sql_dialect_notes.title,
|
|
745
|
+
description: toolDescriptions.sql_dialect_notes,
|
|
746
|
+
inputSchema: sqlDialectNotesSchema.shape,
|
|
747
|
+
outputSchema: sqlDialectNotesOutputSchema,
|
|
748
|
+
annotations: toolAnnotations.sql_dialect_notes,
|
|
749
|
+
}, sqlDialectNotesSchema, async (input) => jsonToolResult(await dialectNotes.read(input)), toolTelemetry);
|
|
750
|
+
}
|
|
657
751
|
if (ports.memoryIngest) {
|
|
658
752
|
const memoryIngest = ports.memoryIngest;
|
|
659
753
|
registerParsedTool(server, 'memory_ingest', {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KtxSqlQueryExecutorPort } from '../../context/connections/query-executor.js';
|
|
2
|
+
import type { KtxProjectConnectionConfig } from '../../context/project/config.js';
|
|
2
3
|
import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
|
|
3
4
|
import type { KtxSemanticLayerComputePort } from '../../context/daemon/semantic-layer-compute.js';
|
|
4
5
|
import type { KtxLocalProject } from '../../context/project/project.js';
|
|
@@ -12,5 +13,11 @@ interface CreateLocalProjectMcpContextPortsOptions {
|
|
|
12
13
|
localScan?: LocalScanMcpOptions;
|
|
13
14
|
embeddingService: KtxEmbeddingPort | null;
|
|
14
15
|
}
|
|
16
|
+
/** @internal Resolves a connection's dialect SQL notes; throws KtxExpectedError for an unknown or non-SQL-warehouse connection. */
|
|
17
|
+
export declare function resolveDialectNotesForConnection(connectionId: string, connection: KtxProjectConnectionConfig | undefined): {
|
|
18
|
+
connectionId: string;
|
|
19
|
+
dialect: string;
|
|
20
|
+
notes: string;
|
|
21
|
+
};
|
|
15
22
|
export declare function createLocalProjectMcpContextPorts(project: KtxLocalProject, options: CreateLocalProjectMcpContextPortsOptions): KtxMcpContextPorts;
|
|
16
23
|
export {};
|