@kaelio/ktx 0.13.1 → 0.15.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.
Files changed (261) hide show
  1. package/assets/python/{kaelio_ktx-0.13.1-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +1 -1
  5. package/dist/commands/ingest-commands.d.ts +9 -0
  6. package/dist/commands/ingest-commands.js +37 -1
  7. package/dist/commands/knowledge-commands.js +3 -0
  8. package/dist/commands/setup-commands.js +15 -1
  9. package/dist/connection-drivers.d.ts +2 -0
  10. package/dist/connection-drivers.js +7 -3
  11. package/dist/connection.d.ts +3 -0
  12. package/dist/connection.js +27 -0
  13. package/dist/connectors/bigquery/connector.d.ts +17 -6
  14. package/dist/connectors/bigquery/connector.js +152 -76
  15. package/dist/connectors/bigquery/dialect.d.ts +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +1 -0
  17. package/dist/connectors/clickhouse/connector.js +45 -16
  18. package/dist/connectors/clickhouse/dialect.d.ts +2 -2
  19. package/dist/connectors/mongodb/connector.d.ts +69 -0
  20. package/dist/connectors/mongodb/connector.js +307 -0
  21. package/dist/connectors/mongodb/dialect.d.ts +17 -0
  22. package/dist/connectors/mongodb/dialect.js +50 -0
  23. package/dist/connectors/mongodb/live-database-introspection.d.ts +10 -0
  24. package/dist/connectors/mongodb/live-database-introspection.js +24 -0
  25. package/dist/connectors/mongodb/schema-inference.d.ts +20 -0
  26. package/dist/connectors/mongodb/schema-inference.js +110 -0
  27. package/dist/connectors/mysql/connector.d.ts +1 -0
  28. package/dist/connectors/mysql/connector.js +18 -2
  29. package/dist/connectors/mysql/dialect.d.ts +2 -2
  30. package/dist/connectors/postgres/connector.d.ts +1 -0
  31. package/dist/connectors/postgres/connector.js +20 -3
  32. package/dist/connectors/postgres/dialect.d.ts +2 -2
  33. package/dist/connectors/snowflake/connector.d.ts +1 -0
  34. package/dist/connectors/snowflake/connector.js +27 -3
  35. package/dist/connectors/snowflake/dialect.d.ts +2 -2
  36. package/dist/connectors/sqlite/connector.d.ts +11 -1
  37. package/dist/connectors/sqlite/connector.js +120 -17
  38. package/dist/connectors/sqlite/dialect.d.ts +2 -2
  39. package/dist/connectors/sqlite/read-query-child.d.ts +1 -0
  40. package/dist/connectors/sqlite/read-query-child.js +23 -0
  41. package/dist/connectors/sqlserver/connector.d.ts +2 -0
  42. package/dist/connectors/sqlserver/connector.js +20 -3
  43. package/dist/connectors/sqlserver/dialect.d.ts +2 -2
  44. package/dist/context/cache/content-result-cache.d.ts +36 -0
  45. package/dist/context/cache/content-result-cache.js +14 -0
  46. package/dist/context/cache/sqlite-content-result-cache.d.ts +18 -0
  47. package/dist/context/cache/sqlite-content-result-cache.js +223 -0
  48. package/dist/context/connections/bigquery-identifiers.d.ts +1 -0
  49. package/dist/context/connections/bigquery-identifiers.js +7 -0
  50. package/dist/context/connections/configured-connections.d.ts +9 -0
  51. package/dist/context/connections/configured-connections.js +18 -0
  52. package/dist/context/connections/dialects.d.ts +30 -4
  53. package/dist/context/connections/dialects.js +38 -11
  54. package/dist/context/connections/drivers.js +21 -0
  55. package/dist/context/connections/gdrive-config.d.ts +19 -0
  56. package/dist/context/connections/gdrive-config.js +57 -0
  57. package/dist/context/connections/query-deadline.d.ts +31 -0
  58. package/dist/context/connections/query-deadline.js +37 -0
  59. package/dist/context/ingest/adapters/gdrive/chunk.d.ts +3 -0
  60. package/dist/context/ingest/adapters/gdrive/chunk.js +74 -0
  61. package/dist/context/ingest/adapters/gdrive/detect.d.ts +1 -0
  62. package/dist/context/ingest/adapters/gdrive/detect.js +20 -0
  63. package/dist/context/ingest/adapters/gdrive/fetch.d.ts +6 -0
  64. package/dist/context/ingest/adapters/gdrive/fetch.js +95 -0
  65. package/dist/context/ingest/adapters/gdrive/gdrive-client.d.ts +30 -0
  66. package/dist/context/ingest/adapters/gdrive/gdrive-client.js +132 -0
  67. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.d.ts +11 -0
  68. package/dist/context/ingest/adapters/gdrive/gdrive.adapter.js +27 -0
  69. package/dist/context/ingest/adapters/gdrive/normalize.d.ts +2 -0
  70. package/dist/context/ingest/adapters/gdrive/normalize.js +256 -0
  71. package/dist/context/ingest/adapters/gdrive/types.d.ts +153 -0
  72. package/dist/context/ingest/adapters/gdrive/types.js +36 -0
  73. package/dist/context/ingest/adapters/live-database/daemon-introspection.js +24 -0
  74. package/dist/context/ingest/adapters/live-database/fetch-report.d.ts +8 -0
  75. package/dist/context/ingest/adapters/live-database/fetch-report.js +37 -0
  76. package/dist/context/ingest/adapters/live-database/live-database.adapter.d.ts +2 -1
  77. package/dist/context/ingest/adapters/live-database/live-database.adapter.js +9 -2
  78. package/dist/context/ingest/adapters/live-database/manifest.d.ts +2 -0
  79. package/dist/context/ingest/adapters/live-database/manifest.js +8 -4
  80. package/dist/context/ingest/adapters/live-database/scan-outcome.d.ts +17 -0
  81. package/dist/context/ingest/adapters/live-database/scan-outcome.js +40 -0
  82. package/dist/context/ingest/adapters/live-database/stage.js +5 -5
  83. package/dist/context/ingest/adapters/metabase/types.d.ts +1 -1
  84. package/dist/context/ingest/adapters/metabase/types.js +1 -1
  85. package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
  86. package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
  87. package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
  88. package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
  89. package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
  90. package/dist/context/ingest/adapters/sigma/client.js +176 -0
  91. package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
  92. package/dist/context/ingest/adapters/sigma/detect.js +23 -0
  93. package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
  94. package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
  95. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
  96. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
  97. package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
  98. package/dist/context/ingest/adapters/sigma/project.js +186 -0
  99. package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
  100. package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
  101. package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
  102. package/dist/context/ingest/adapters/sigma/types.js +82 -0
  103. package/dist/context/ingest/artifact-gates.d.ts +32 -1
  104. package/dist/context/ingest/artifact-gates.js +85 -18
  105. package/dist/context/ingest/final-gate-prune.d.ts +35 -0
  106. package/dist/context/ingest/final-gate-prune.js +229 -0
  107. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  108. package/dist/context/ingest/ingest-bundle.runner.js +459 -240
  109. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +0 -11
  110. package/dist/context/ingest/isolated-diff/patch-integrator.js +0 -44
  111. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  112. package/dist/context/ingest/isolated-diff/work-unit-executor.js +13 -4
  113. package/dist/context/ingest/local-adapters.d.ts +2 -1
  114. package/dist/context/ingest/local-adapters.js +28 -0
  115. package/dist/context/ingest/local-bundle-runtime.js +7 -2
  116. package/dist/context/ingest/local-ingest.js +1 -1
  117. package/dist/context/ingest/local-stage-ingest.d.ts +3 -1
  118. package/dist/context/ingest/local-stage-ingest.js +3 -1
  119. package/dist/context/ingest/ports.d.ts +3 -0
  120. package/dist/context/ingest/report-snapshot.js +13 -3
  121. package/dist/context/ingest/reports.d.ts +3 -3
  122. package/dist/context/ingest/reports.js +3 -1
  123. package/dist/context/ingest/stages/build-wu-context.d.ts +1 -0
  124. package/dist/context/ingest/stages/build-wu-context.js +2 -1
  125. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -1
  126. package/dist/context/ingest/stages/stage-3-work-units.js +4 -10
  127. package/dist/context/ingest/stages/validate-wu-sources.d.ts +12 -0
  128. package/dist/context/ingest/stages/validate-wu-sources.js +23 -8
  129. package/dist/context/ingest/tools/read-raw-file.tool.js +10 -5
  130. package/dist/context/ingest/tools/read-raw-span.tool.js +10 -5
  131. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +1 -1
  132. package/dist/context/ingest/tools/warehouse-verification/entity-details.tool.js +1 -1
  133. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +1 -1
  134. package/dist/context/ingest/types.d.ts +3 -0
  135. package/dist/context/ingest/wiki-body-refs.d.ts +28 -0
  136. package/dist/context/ingest/wiki-body-refs.js +37 -8
  137. package/dist/context/ingest/wiki-sl-ref-repair.d.ts +1 -0
  138. package/dist/context/ingest/wiki-sl-ref-repair.js +4 -0
  139. package/dist/context/ingest/work-unit-cache.d.ts +67 -0
  140. package/dist/context/ingest/work-unit-cache.js +230 -0
  141. package/dist/context/llm/ai-sdk-runtime.d.ts +1 -0
  142. package/dist/context/llm/ai-sdk-runtime.js +5 -0
  143. package/dist/context/llm/claude-code-runtime.d.ts +4 -1
  144. package/dist/context/llm/claude-code-runtime.js +38 -8
  145. package/dist/context/llm/codex-runtime.d.ts +4 -1
  146. package/dist/context/llm/codex-runtime.js +48 -35
  147. package/dist/context/llm/runtime-port.d.ts +26 -0
  148. package/dist/context/llm/subprocess-generate-object-child.d.ts +1 -0
  149. package/dist/context/llm/subprocess-generate-object-child.js +34 -0
  150. package/dist/context/llm/subprocess-generate-object.d.ts +42 -0
  151. package/dist/context/llm/subprocess-generate-object.js +122 -0
  152. package/dist/context/mcp/context-tools.d.ts +2 -0
  153. package/dist/context/mcp/context-tools.js +111 -17
  154. package/dist/context/mcp/local-project-ports.d.ts +7 -0
  155. package/dist/context/mcp/local-project-ports.js +29 -0
  156. package/dist/context/mcp/logger.d.ts +24 -0
  157. package/dist/context/mcp/logger.js +49 -0
  158. package/dist/context/mcp/server.js +2 -0
  159. package/dist/context/mcp/types.d.ts +17 -0
  160. package/dist/context/memory/memory-agent.service.js +1 -1
  161. package/dist/context/project/config.d.ts +72 -0
  162. package/dist/context/project/config.js +5 -0
  163. package/dist/context/project/driver-schemas.d.ts +35 -0
  164. package/dist/context/project/driver-schemas.js +89 -1
  165. package/dist/context/project/project.js +1 -1
  166. package/dist/context/project/setup-config.js +1 -0
  167. package/dist/context/scan/description-generation.d.ts +4 -0
  168. package/dist/context/scan/description-generation.js +100 -13
  169. package/dist/context/scan/enabled-tables.d.ts +5 -0
  170. package/dist/context/scan/enabled-tables.js +13 -1
  171. package/dist/context/scan/enrichment-state.d.ts +50 -7
  172. package/dist/context/scan/enrichment-state.js +30 -13
  173. package/dist/context/scan/local-enrichment-artifacts.d.ts +41 -0
  174. package/dist/context/scan/local-enrichment-artifacts.js +155 -32
  175. package/dist/context/scan/local-enrichment.d.ts +39 -4
  176. package/dist/context/scan/local-enrichment.js +345 -92
  177. package/dist/context/scan/local-scan.d.ts +4 -1
  178. package/dist/context/scan/local-scan.js +54 -13
  179. package/dist/context/scan/local-structural-artifacts.d.ts +3 -1
  180. package/dist/context/scan/local-structural-artifacts.js +5 -0
  181. package/dist/context/scan/object-introspection.d.ts +21 -0
  182. package/dist/context/scan/object-introspection.js +35 -0
  183. package/dist/context/scan/relationship-benchmarks.js +5 -3
  184. package/dist/context/scan/relationship-composite-candidates.d.ts +6 -3
  185. package/dist/context/scan/relationship-composite-candidates.js +13 -1
  186. package/dist/context/scan/relationship-detection-budget.d.ts +35 -0
  187. package/dist/context/scan/relationship-detection-budget.js +53 -0
  188. package/dist/context/scan/relationship-diagnostics.d.ts +5 -0
  189. package/dist/context/scan/relationship-diagnostics.js +2 -0
  190. package/dist/context/scan/relationship-discovery.d.ts +9 -3
  191. package/dist/context/scan/relationship-discovery.js +27 -4
  192. package/dist/context/scan/relationship-llm-proposal.js +36 -27
  193. package/dist/context/scan/relationship-profiling.d.ts +7 -3
  194. package/dist/context/scan/relationship-profiling.js +53 -41
  195. package/dist/context/scan/relationship-validation.d.ts +6 -4
  196. package/dist/context/scan/relationship-validation.js +46 -32
  197. package/dist/context/scan/sqlite-local-enrichment-state-store.d.ts +8 -1
  198. package/dist/context/scan/sqlite-local-enrichment-state-store.js +71 -159
  199. package/dist/context/scan/types.d.ts +5 -3
  200. package/dist/context/scan/types.js +2 -0
  201. package/dist/context/sl/local-query.js +5 -0
  202. package/dist/context/sl/pglite-sl-search-prototype.js +1 -1
  203. package/dist/context/sl/semantic-layer.service.js +1 -1
  204. package/dist/context/sl/source-files.d.ts +5 -0
  205. package/dist/context/sl/source-files.js +17 -11
  206. package/dist/context/sl/tools/connection-id-schema.js +1 -1
  207. package/dist/context/sql-analysis/dialect-notes.d.ts +9 -0
  208. package/dist/context/sql-analysis/dialect-notes.js +40 -0
  209. package/dist/context/sql-analysis/dialects/bigquery.md +13 -0
  210. package/dist/context/sql-analysis/dialects/clickhouse.md +9 -0
  211. package/dist/context/sql-analysis/dialects/mysql.md +9 -0
  212. package/dist/context/sql-analysis/dialects/postgres.md +10 -0
  213. package/dist/context/sql-analysis/dialects/snowflake.md +10 -0
  214. package/dist/context/sql-analysis/dialects/sqlite.md +11 -0
  215. package/dist/context/sql-analysis/dialects/tsql.md +10 -0
  216. package/dist/context/wiki/keys.js +1 -1
  217. package/dist/context/wiki/knowledge-wiki.service.d.ts +4 -4
  218. package/dist/context/wiki/knowledge-wiki.service.js +2 -1
  219. package/dist/context/wiki/local-knowledge.d.ts +13 -0
  220. package/dist/context/wiki/local-knowledge.js +50 -6
  221. package/dist/context/wiki/sqlite-knowledge-index.d.ts +2 -0
  222. package/dist/context/wiki/sqlite-knowledge-index.js +21 -5
  223. package/dist/context/wiki/tools/wiki-write.tool.d.ts +2 -0
  224. package/dist/context/wiki/tools/wiki-write.tool.js +27 -0
  225. package/dist/context/wiki/types.d.ts +6 -0
  226. package/dist/context-build-view.d.ts +3 -0
  227. package/dist/context-build-view.js +1 -0
  228. package/dist/knowledge.d.ts +2 -0
  229. package/dist/knowledge.js +12 -1
  230. package/dist/local-adapters.js +11 -0
  231. package/dist/mcp-http-server.js +15 -1
  232. package/dist/mcp-server-factory.d.ts +2 -0
  233. package/dist/mcp-server-factory.js +15 -1
  234. package/dist/mcp-stdio-server.js +10 -2
  235. package/dist/notion-page-picker.js +1 -1
  236. package/dist/prompts/memory_agent_external_ingest.md +2 -0
  237. package/dist/public-ingest.d.ts +3 -1
  238. package/dist/public-ingest.js +4 -0
  239. package/dist/scan.d.ts +3 -1
  240. package/dist/scan.js +7 -0
  241. package/dist/setup-databases.d.ts +1 -1
  242. package/dist/setup-databases.js +43 -1
  243. package/dist/setup-sources.d.ts +6 -1
  244. package/dist/setup-sources.js +208 -48
  245. package/dist/setup.d.ts +3 -0
  246. package/dist/setup.js +6 -1
  247. package/dist/skills/analytics/SKILL.md +200 -2
  248. package/dist/skills/gdrive_synthesize/SKILL.md +97 -0
  249. package/dist/skills/sigma_ingest/SKILL.md +189 -0
  250. package/dist/skills/wiki_capture/SKILL.md +24 -0
  251. package/dist/sql.js +4 -0
  252. package/dist/status-project.d.ts +4 -0
  253. package/dist/status-project.js +54 -2
  254. package/dist/telemetry/events.d.ts +2 -2
  255. package/dist/text-ingest.d.ts +4 -0
  256. package/dist/text-ingest.js +58 -29
  257. package/dist/verbatim-ingest.d.ts +46 -0
  258. package/dist/verbatim-ingest.js +193 -0
  259. package/package.json +5 -1
  260. package/dist/context/ingest/final-gate-repair.d.ts +0 -28
  261. package/dist/context/ingest/final-gate-repair.js +0 -91
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { type CodexRuntimeMcpServerHandle } from './codex-mcp-runtime-server.js';
3
3
  import { type CodexSdkRunner } from './codex-sdk-runner.js';
4
4
  import type { RateLimitGovernor } from './rate-limit-governor.js';
5
- import type { KtxGenerateObjectInput, KtxGenerateTextInput, KtxLlmRuntimePort, KtxRuntimeToolSet, RunLoopParams, RunLoopResult } from './runtime-port.js';
5
+ import type { KtxGenerateObjectInput, KtxGenerateStructuredJsonInput, KtxGenerateTextInput, KtxLlmRuntimePort, KtxRuntimeToolSet, RunLoopParams, RunLoopResult, SubprocessRuntimeForkSpec } from './runtime-port.js';
6
6
  export interface CodexKtxLlmRuntimeDeps {
7
7
  projectDir: string;
8
8
  modelSlots: {
@@ -21,7 +21,10 @@ export declare class CodexKtxLlmRuntime implements KtxLlmRuntimePort {
21
21
  constructor(deps: CodexKtxLlmRuntimeDeps);
22
22
  private runWithRateLimitRetry;
23
23
  generateText(input: KtxGenerateTextInput): Promise<string>;
24
+ private streamStructuredText;
24
25
  generateObject<TOutput, TSchema extends z.ZodType<TOutput>>(input: KtxGenerateObjectInput<TOutput, TSchema>): Promise<TOutput>;
26
+ generateStructuredJson(input: KtxGenerateStructuredJsonInput): Promise<unknown>;
27
+ subprocessForkSpec(): SubprocessRuntimeForkSpec;
25
28
  runAgentLoop(params: RunLoopParams): Promise<RunLoopResult>;
26
29
  }
27
30
  export declare function runCodexAuthProbe(input: {
@@ -185,48 +185,61 @@ export class CodexKtxLlmRuntime {
185
185
  await mcp?.close();
186
186
  }
187
187
  }
188
- async generateObject(input) {
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 mcp = await mcpForTools({
192
- projectDir: this.deps.projectDir,
193
- toolSet: input.tools,
194
- startMcpServer: this.deps.startMcpServer,
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
- const config = buildCodexRuntimeConfig({
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
- finally {
227
- await mcp?.close();
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,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, telemetry) {
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
- if (telemetry.io && telemetry.projectDir && shouldEmitMcpTelemetry()) {
474
- const isError = typeof result === 'object' && result !== null && 'isError' in result && result.isError === true;
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: telemetry.projectDir,
479
- io: telemetry.io,
543
+ projectDir: deps.projectDir,
544
+ io: deps.io,
480
545
  fields: {
481
546
  toolName: name,
482
547
  outcome: isError ? 'error' : 'ok',
483
- durationMs: Math.max(0, performance.now() - startedAt),
548
+ durationMs,
484
549
  sampleRate: mcpTelemetrySampleRate(),
485
550
  ...(errorDetail ? { errorDetail } : {}),
486
- ...clientTelemetryFields(telemetry.getClientInfo),
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
- if (telemetry.io) {
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: telemetry.projectDir,
498
- io: telemetry.io,
577
+ projectDir: deps.projectDir,
578
+ io: deps.io,
499
579
  redactionSecrets: await collectTelemetryRedactionSecrets({
500
- projectDir: telemetry.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 (telemetry.io && telemetry.projectDir && shouldEmitMcpTelemetry()) {
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: telemetry.projectDir,
513
- io: telemetry.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: Math.max(0, performance.now() - startedAt),
599
+ durationMs,
520
600
  sampleRate: mcpTelemetrySampleRate(),
521
- ...clientTelemetryFields(telemetry.getClientInfo),
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 {};