@kaelio/ktx 0.8.0 → 0.10.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 (183) hide show
  1. package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/clack.d.ts +6 -0
  5. package/dist/clack.js +17 -2
  6. package/dist/cli-program.d.ts +3 -0
  7. package/dist/cli-program.js +42 -2
  8. package/dist/cli-runtime.d.ts +3 -0
  9. package/dist/cli-runtime.js +94 -3
  10. package/dist/commands/setup-commands.js +3 -4
  11. package/dist/connection-recovery.d.ts +34 -0
  12. package/dist/connection-recovery.js +82 -0
  13. package/dist/connection.js +26 -2
  14. package/dist/connectors/bigquery/connector.d.ts +2 -5
  15. package/dist/connectors/bigquery/connector.js +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +2 -5
  17. package/dist/connectors/clickhouse/connector.js +2 -2
  18. package/dist/connectors/mysql/connector.d.ts +7 -6
  19. package/dist/connectors/mysql/connector.js +25 -5
  20. package/dist/connectors/mysql/dialect.d.ts +1 -1
  21. package/dist/connectors/mysql/dialect.js +12 -2
  22. package/dist/connectors/postgres/connector.d.ts +2 -5
  23. package/dist/connectors/postgres/connector.js +2 -2
  24. package/dist/connectors/snowflake/connector.d.ts +2 -5
  25. package/dist/connectors/snowflake/connector.js +2 -2
  26. package/dist/connectors/sqlite/connector.d.ts +2 -5
  27. package/dist/connectors/sqlite/connector.js +2 -2
  28. package/dist/connectors/sqlserver/connector.d.ts +2 -5
  29. package/dist/connectors/sqlserver/connector.js +2 -2
  30. package/dist/context/connections/drivers.d.ts +0 -1
  31. package/dist/context/connections/drivers.js +0 -7
  32. package/dist/context/connections/query-executor.d.ts +2 -1
  33. package/dist/context/core/abort.d.ts +9 -0
  34. package/dist/context/core/abort.js +36 -0
  35. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  36. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  37. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  38. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  39. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  40. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +30 -0
  41. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +194 -0
  42. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  43. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  44. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  45. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  46. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  47. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  48. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  49. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  50. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  51. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  52. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  53. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  54. package/dist/context/ingest/final-gate-repair.js +1 -0
  55. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  56. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  57. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  58. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  59. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  60. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  61. package/dist/context/ingest/local-adapters.js +21 -4
  62. package/dist/context/ingest/local-bundle-runtime.js +13 -5
  63. package/dist/context/ingest/local-ingest.d.ts +1 -0
  64. package/dist/context/ingest/local-ingest.js +13 -3
  65. package/dist/context/ingest/memory-flow/events.js +1 -1
  66. package/dist/context/ingest/memory-flow/schema.js +8 -3
  67. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  68. package/dist/context/ingest/ports.d.ts +3 -5
  69. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  70. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  71. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  72. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  73. package/dist/context/ingest/types.d.ts +1 -0
  74. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  75. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  76. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  77. package/dist/context/llm/claude-code-runtime.js +127 -48
  78. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  79. package/dist/context/llm/codex-exec-events.js +155 -0
  80. package/dist/context/llm/codex-isolation.d.ts +3 -0
  81. package/dist/context/llm/codex-isolation.js +5 -0
  82. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  83. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  84. package/dist/context/llm/codex-models.d.ts +2 -0
  85. package/dist/context/llm/codex-models.js +17 -0
  86. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  87. package/dist/context/llm/codex-runtime-config.js +19 -0
  88. package/dist/context/llm/codex-runtime.d.ts +37 -0
  89. package/dist/context/llm/codex-runtime.js +347 -0
  90. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  91. package/dist/context/llm/codex-sdk-runner.js +63 -0
  92. package/dist/context/llm/local-config.d.ts +16 -4
  93. package/dist/context/llm/local-config.js +18 -2
  94. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  95. package/dist/context/llm/rate-limit-governor.js +285 -0
  96. package/dist/context/llm/runtime-port.d.ts +3 -6
  97. package/dist/context/mcp/context-tools.js +43 -13
  98. package/dist/context/project/config.d.ts +14 -0
  99. package/dist/context/project/config.js +37 -2
  100. package/dist/context/scan/types.d.ts +15 -2
  101. package/dist/context/scan/types.js +12 -0
  102. package/dist/context/sl/description-normalization.js +4 -14
  103. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  104. package/dist/context/sql-analysis/ports.d.ts +12 -2
  105. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  106. package/dist/context-build-view.d.ts +13 -0
  107. package/dist/context-build-view.js +63 -32
  108. package/dist/demo-metrics.d.ts +0 -2
  109. package/dist/demo-metrics.js +1 -11
  110. package/dist/ingest.d.ts +1 -0
  111. package/dist/ingest.js +32 -3
  112. package/dist/io/buffered-command-io.d.ts +11 -0
  113. package/dist/io/buffered-command-io.js +28 -0
  114. package/dist/io/symbols.d.ts +2 -0
  115. package/dist/io/symbols.js +2 -0
  116. package/dist/llm/types.d.ts +1 -1
  117. package/dist/local-adapters.d.ts +10 -2
  118. package/dist/local-adapters.js +19 -3
  119. package/dist/memory-flow-hud.js +8 -16
  120. package/dist/next-steps.js +1 -2
  121. package/dist/progress-port-adapter.d.ts +6 -0
  122. package/dist/progress-port-adapter.js +18 -0
  123. package/dist/public-ingest.d.ts +20 -1
  124. package/dist/public-ingest.js +228 -42
  125. package/dist/reveal-password-prompt.d.ts +24 -0
  126. package/dist/reveal-password-prompt.js +78 -0
  127. package/dist/scan.js +21 -3
  128. package/dist/setup-context.d.ts +2 -0
  129. package/dist/setup-context.js +133 -27
  130. package/dist/setup-databases.d.ts +18 -1
  131. package/dist/setup-databases.js +378 -249
  132. package/dist/setup-demo-tour.js +1 -0
  133. package/dist/setup-embeddings.js +1 -1
  134. package/dist/setup-models.d.ts +11 -15
  135. package/dist/setup-models.js +140 -276
  136. package/dist/setup-prompts.js +3 -2
  137. package/dist/setup-ready-menu.d.ts +16 -2
  138. package/dist/setup-ready-menu.js +37 -5
  139. package/dist/setup-sources.js +115 -35
  140. package/dist/setup.d.ts +1 -1
  141. package/dist/setup.js +23 -11
  142. package/dist/sl.d.ts +2 -2
  143. package/dist/sl.js +20 -4
  144. package/dist/sql.js +18 -2
  145. package/dist/star-prompt/cache.d.ts +16 -0
  146. package/dist/star-prompt/cache.js +45 -0
  147. package/dist/star-prompt/star-count.d.ts +7 -0
  148. package/dist/star-prompt/star-count.js +66 -0
  149. package/dist/star-prompt/star-line.d.ts +12 -0
  150. package/dist/star-prompt/star-line.js +26 -0
  151. package/dist/status-project.d.ts +11 -0
  152. package/dist/status-project.js +50 -1
  153. package/dist/telemetry/command-hook.d.ts +1 -0
  154. package/dist/telemetry/command-hook.js +3 -1
  155. package/dist/telemetry/emitter.d.ts +10 -0
  156. package/dist/telemetry/emitter.js +31 -0
  157. package/dist/telemetry/events.d.ts +35 -6
  158. package/dist/telemetry/events.js +25 -2
  159. package/dist/telemetry/exception.d.ts +18 -0
  160. package/dist/telemetry/exception.js +162 -0
  161. package/dist/telemetry/identity.d.ts +0 -1
  162. package/dist/telemetry/identity.js +6 -6
  163. package/dist/telemetry/index.d.ts +15 -2
  164. package/dist/telemetry/index.js +15 -3
  165. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  166. package/dist/telemetry/redaction-secrets.js +92 -0
  167. package/dist/telemetry/scrubber.d.ts +10 -0
  168. package/dist/telemetry/scrubber.js +20 -0
  169. package/dist/update-check/cache.d.ts +21 -0
  170. package/dist/update-check/cache.js +38 -0
  171. package/dist/update-check/channel.d.ts +15 -0
  172. package/dist/update-check/channel.js +30 -0
  173. package/dist/update-check/registry.d.ts +1 -0
  174. package/dist/update-check/registry.js +45 -0
  175. package/dist/update-check/update-check.d.ts +43 -0
  176. package/dist/update-check/update-check.js +116 -0
  177. package/package.json +12 -4
  178. package/dist/context/connections/local-query-executor.d.ts +0 -6
  179. package/dist/context/connections/local-query-executor.js +0 -39
  180. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  181. package/dist/context/connections/postgres-query-executor.js +0 -53
  182. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  183. package/dist/context/connections/sqlite-query-executor.js +0 -74
@@ -0,0 +1,285 @@
1
+ import { createAbortError, throwIfAborted } from '../core/abort.js';
2
+ const defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
3
+ if (signal?.aborted) {
4
+ reject(createAbortError());
5
+ return;
6
+ }
7
+ const timeout = setTimeout(resolve, ms);
8
+ signal?.addEventListener('abort', () => {
9
+ clearTimeout(timeout);
10
+ reject(createAbortError());
11
+ }, { once: true });
12
+ });
13
+ export function createRateLimitGovernorConfig(input = {}) {
14
+ return {
15
+ enabled: input.enabled ?? true,
16
+ maxConcurrency: input.maxConcurrency ?? 1,
17
+ throttleThreshold: input.throttleThreshold ?? 0.8,
18
+ minConcurrencyUnderPressure: input.minConcurrencyUnderPressure ?? 1,
19
+ ...(input.maxWaitMs !== undefined ? { maxWaitMs: input.maxWaitMs } : {}),
20
+ waitStateTickMs: input.waitStateTickMs ?? 1_000,
21
+ retry: {
22
+ maxAttempts: input.retry?.maxAttempts ?? 6,
23
+ baseDelayMs: input.retry?.baseDelayMs ?? 1_000,
24
+ maxDelayMs: input.retry?.maxDelayMs ?? 60_000,
25
+ jitter: input.retry?.jitter ?? true,
26
+ },
27
+ };
28
+ }
29
+ export class RateLimitGovernor {
30
+ config;
31
+ now;
32
+ sleep;
33
+ random;
34
+ subscribers = new Set();
35
+ waiters = [];
36
+ active = 0;
37
+ effectiveLimit;
38
+ pausedUntilMs = null;
39
+ pausedProvider = null;
40
+ pausedRateLimitType;
41
+ pausedTickMs = null;
42
+ opaqueAttempts = new Map();
43
+ pauseGeneration = 0;
44
+ visibleWaitAbort = null;
45
+ constructor(config, deps = {}) {
46
+ this.config = config;
47
+ this.now = deps.now ?? Date.now;
48
+ this.sleep = deps.sleep ?? defaultSleep;
49
+ this.random = deps.random ?? Math.random;
50
+ this.effectiveLimit = Math.max(1, config.maxConcurrency);
51
+ }
52
+ currentLimit() {
53
+ return this.config.enabled ? this.effectiveLimit : this.config.maxConcurrency;
54
+ }
55
+ /**
56
+ * Total attempts a runtime should make for a single rate-limited LLM call,
57
+ * including the first try. Returns 1 (no outer retry) when pacing is disabled:
58
+ * the outer retry loop only exists to cooperate with this governor's pause, so
59
+ * without active pacing there is no backoff to apply and the backend's own
60
+ * retry handles transient rejections.
61
+ */
62
+ maxRetryAttempts() {
63
+ return this.config.enabled ? Math.max(1, this.config.retry.maxAttempts) : 1;
64
+ }
65
+ activeSlots() {
66
+ return this.active;
67
+ }
68
+ subscribe(cb) {
69
+ this.subscribers.add(cb);
70
+ if (this.pausedUntilMs !== null) {
71
+ this.startVisibleWaitTicker();
72
+ }
73
+ return () => {
74
+ this.subscribers.delete(cb);
75
+ if (this.subscribers.size === 0) {
76
+ this.stopVisibleWaitTicker();
77
+ this.wakeWaiters();
78
+ }
79
+ };
80
+ }
81
+ report(signal) {
82
+ if (!this.config.enabled) {
83
+ return;
84
+ }
85
+ this.emit({
86
+ kind: 'rate_limit_observed',
87
+ provider: signal.provider,
88
+ status: signal.status,
89
+ ...(signal.rateLimitType ? { rateLimitType: signal.rateLimitType } : {}),
90
+ ...(signal.resetAtMs !== undefined ? { resetAtMs: signal.resetAtMs } : {}),
91
+ ...(signal.retryAfterMs !== undefined ? { retryAfterMs: signal.retryAfterMs } : {}),
92
+ ...(signal.utilization !== undefined ? { utilization: signal.utilization } : {}),
93
+ });
94
+ if (signal.status === 'rejected') {
95
+ this.applyPause(signal);
96
+ return;
97
+ }
98
+ if (signal.status === 'warning' || (signal.utilization ?? 0) >= this.config.throttleThreshold) {
99
+ this.adjustLimit(Math.max(1, this.config.minConcurrencyUnderPressure), signal, 'provider pressure');
100
+ return;
101
+ }
102
+ this.opaqueAttempts.delete(signal.provider);
103
+ if ((signal.utilization ?? 0) < this.config.throttleThreshold) {
104
+ this.adjustLimit(Math.max(1, this.config.maxConcurrency), signal, 'provider recovered');
105
+ }
106
+ }
107
+ async waitForReady(signal) {
108
+ throwIfAborted(signal);
109
+ if (!this.config.enabled) {
110
+ return;
111
+ }
112
+ await this.waitForPause(signal);
113
+ throwIfAborted(signal);
114
+ }
115
+ async acquireWorkSlot(signal) {
116
+ throwIfAborted(signal);
117
+ if (!this.config.enabled) {
118
+ this.active += 1;
119
+ return () => {
120
+ this.active -= 1;
121
+ };
122
+ }
123
+ while (true) {
124
+ throwIfAborted(signal);
125
+ await this.waitForPause(signal);
126
+ throwIfAborted(signal);
127
+ if (this.active < this.effectiveLimit) {
128
+ this.active += 1;
129
+ let released = false;
130
+ return () => {
131
+ if (released)
132
+ return;
133
+ released = true;
134
+ this.active -= 1;
135
+ this.wakeWaiters();
136
+ };
137
+ }
138
+ await this.waitForSlot(signal);
139
+ }
140
+ }
141
+ applyPause(signal) {
142
+ const resumeAtMs = this.resumeAtMsFor(signal);
143
+ const boundedResumeAtMs = this.config.maxWaitMs === undefined ? resumeAtMs : Math.min(resumeAtMs, this.now() + this.config.maxWaitMs);
144
+ if (this.pausedUntilMs === null || boundedResumeAtMs > this.pausedUntilMs) {
145
+ this.pausedUntilMs = boundedResumeAtMs;
146
+ this.pausedProvider = signal.provider;
147
+ this.pausedRateLimitType = signal.rateLimitType;
148
+ this.pausedTickMs = signal.rateLimitType === 'opaque' ? Math.max(1, boundedResumeAtMs - this.now()) : null;
149
+ this.emitWait('wait_started');
150
+ this.startVisibleWaitTicker();
151
+ this.wakeWaiters();
152
+ }
153
+ this.adjustLimit(Math.max(1, this.config.minConcurrencyUnderPressure), signal, 'provider rejected');
154
+ }
155
+ resumeAtMsFor(signal) {
156
+ if (signal.resetAtMs !== undefined) {
157
+ return signal.resetAtMs;
158
+ }
159
+ if (signal.retryAfterMs !== undefined) {
160
+ return this.now() + signal.retryAfterMs;
161
+ }
162
+ const attempts = this.opaqueAttempts.get(signal.provider) ?? 0;
163
+ this.opaqueAttempts.set(signal.provider, Math.min(attempts + 1, this.config.retry.maxAttempts));
164
+ const base = Math.min(this.config.retry.maxDelayMs, this.config.retry.baseDelayMs * 2 ** Math.min(attempts, this.config.retry.maxAttempts - 1));
165
+ const jitterMultiplier = this.config.retry.jitter ? 0.75 + this.random() * 0.5 : 1;
166
+ return this.now() + Math.round(base * jitterMultiplier);
167
+ }
168
+ adjustLimit(to, signal, reason) {
169
+ const bounded = Math.max(1, Math.min(this.config.maxConcurrency, to));
170
+ if (bounded === this.effectiveLimit) {
171
+ return;
172
+ }
173
+ const from = this.effectiveLimit;
174
+ this.effectiveLimit = bounded;
175
+ this.emit({
176
+ kind: 'concurrency_adjusted',
177
+ provider: signal.provider,
178
+ from,
179
+ to: bounded,
180
+ reason,
181
+ ...(signal.rateLimitType ? { rateLimitType: signal.rateLimitType } : {}),
182
+ ...(signal.utilization !== undefined ? { utilization: signal.utilization } : {}),
183
+ });
184
+ this.wakeWaiters();
185
+ }
186
+ startVisibleWaitTicker() {
187
+ if (this.subscribers.size === 0 || this.pausedUntilMs === null) {
188
+ return;
189
+ }
190
+ this.stopVisibleWaitTicker();
191
+ const generation = (this.pauseGeneration += 1);
192
+ const controller = new AbortController();
193
+ this.visibleWaitAbort = controller;
194
+ void this.runVisibleWaitTicker(generation, controller.signal).catch(() => undefined);
195
+ }
196
+ stopVisibleWaitTicker() {
197
+ this.visibleWaitAbort?.abort();
198
+ this.visibleWaitAbort = null;
199
+ }
200
+ async runVisibleWaitTicker(generation, signal) {
201
+ while (!signal.aborted && generation === this.pauseGeneration && this.pausedUntilMs !== null) {
202
+ const remainingMs = this.pausedUntilMs - this.now();
203
+ if (remainingMs <= 0) {
204
+ this.finishPause(generation);
205
+ return;
206
+ }
207
+ this.emitWait('wait_tick');
208
+ await this.sleep(Math.min(this.pausedTickMs ?? this.config.waitStateTickMs, remainingMs), signal);
209
+ }
210
+ }
211
+ finishPause(generation) {
212
+ if (generation !== undefined && generation !== this.pauseGeneration) {
213
+ return;
214
+ }
215
+ this.emitWait('wait_finished');
216
+ this.pausedUntilMs = null;
217
+ this.pausedProvider = null;
218
+ this.pausedRateLimitType = undefined;
219
+ this.pausedTickMs = null;
220
+ this.stopVisibleWaitTicker();
221
+ this.wakeWaiters();
222
+ }
223
+ async waitForPause(signal) {
224
+ throwIfAborted(signal);
225
+ while (this.pausedUntilMs !== null) {
226
+ const remainingMs = this.pausedUntilMs - this.now();
227
+ if (remainingMs <= 0) {
228
+ this.finishPause();
229
+ return;
230
+ }
231
+ if (this.visibleWaitAbort !== null) {
232
+ await this.waitForSlot(signal);
233
+ }
234
+ else {
235
+ await this.sleep(Math.min(this.pausedTickMs ?? this.config.waitStateTickMs, remainingMs), signal);
236
+ }
237
+ throwIfAborted(signal);
238
+ }
239
+ }
240
+ waitForSlot(signal) {
241
+ if (signal?.aborted) {
242
+ return Promise.reject(createAbortError());
243
+ }
244
+ return new Promise((resolve, reject) => {
245
+ const wake = () => {
246
+ cleanup();
247
+ resolve();
248
+ };
249
+ const onAbort = () => {
250
+ cleanup();
251
+ reject(createAbortError());
252
+ };
253
+ const cleanup = () => {
254
+ this.waiters = this.waiters.filter((candidate) => candidate !== wake);
255
+ signal?.removeEventListener('abort', onAbort);
256
+ };
257
+ this.waiters.push(wake);
258
+ signal?.addEventListener('abort', onAbort, { once: true });
259
+ });
260
+ }
261
+ wakeWaiters() {
262
+ const waiters = this.waiters;
263
+ this.waiters = [];
264
+ for (const waiter of waiters) {
265
+ waiter();
266
+ }
267
+ }
268
+ emitWait(kind) {
269
+ if (this.pausedUntilMs === null || this.pausedProvider === null) {
270
+ return;
271
+ }
272
+ this.emit({
273
+ kind,
274
+ provider: this.pausedProvider,
275
+ ...(this.pausedRateLimitType ? { rateLimitType: this.pausedRateLimitType } : {}),
276
+ resumeAtMs: this.pausedUntilMs,
277
+ remainingMs: Math.max(0, this.pausedUntilMs - this.now()),
278
+ });
279
+ }
280
+ emit(state) {
281
+ for (const subscriber of this.subscribers) {
282
+ subscriber(state);
283
+ }
284
+ }
285
+ }
@@ -12,11 +12,6 @@ export interface KtxRuntimeToolDescriptor<TInput = unknown, TOutput = unknown> {
12
12
  }
13
13
  export type KtxRuntimeToolSet = Record<string, KtxRuntimeToolDescriptor>;
14
14
  export type RunLoopStopReason = 'budget' | 'natural' | 'error';
15
- /** @internal */
16
- export interface RunLoopStepInfo {
17
- stepIndex: number;
18
- stepBudget: number;
19
- }
20
15
  export interface LlmTokenUsage {
21
16
  inputTokens?: number;
22
17
  outputTokens?: number;
@@ -40,7 +35,7 @@ export interface RunLoopParams {
40
35
  toolSet: KtxRuntimeToolSet;
41
36
  stepBudget: number;
42
37
  telemetryTags: Record<string, string>;
43
- onStepFinish?: (info: RunLoopStepInfo) => void | Promise<void>;
38
+ abortSignal?: AbortSignal;
44
39
  }
45
40
  export interface RunLoopResult {
46
41
  stopReason: RunLoopStopReason;
@@ -57,6 +52,7 @@ export interface KtxGenerateTextInput {
57
52
  totalMs: number;
58
53
  usage: LlmTokenUsage;
59
54
  }) => void;
55
+ abortSignal?: AbortSignal;
60
56
  }
61
57
  export interface KtxGenerateObjectInput<TOutput, TSchema extends z.ZodType<TOutput>> {
62
58
  role: KtxModelRole;
@@ -69,6 +65,7 @@ export interface KtxGenerateObjectInput<TOutput, TSchema extends z.ZodType<TOutp
69
65
  totalMs: number;
70
66
  usage: LlmTokenUsage;
71
67
  }) => void;
68
+ abortSignal?: AbortSignal;
72
69
  }
73
70
  export interface KtxLlmRuntimePort {
74
71
  generateText(input: KtxGenerateTextInput): Promise<string>;
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { emitTelemetryEvent, mcpTelemetrySampleRate, shouldEmitMcpTelemetry } from '../../telemetry/index.js';
3
+ import { emitTelemetryEvent, mcpTelemetrySampleRate, reportException, shouldEmitMcpTelemetry, } from '../../telemetry/index.js';
4
+ import { collectTelemetryRedactionSecrets } from '../../telemetry/redaction-secrets.js';
4
5
  import { scrubErrorClass } from '../../telemetry/scrubber.js';
5
6
  const connectionIdSchema = z.string().min(1);
6
7
  const unknownRecordSchema = z.record(z.string(), z.unknown());
@@ -405,12 +406,26 @@ function mcpProgressCallback(context) {
405
406
  });
406
407
  };
407
408
  }
408
- function registerParsedTool(server, name, config, schema, handler) {
409
+ function registerParsedTool(server, name, config, schema, handler, telemetry) {
409
410
  server.registerTool(name, config, async (input, context) => {
410
411
  try {
411
412
  return await handler(schema.parse(input), context);
412
413
  }
413
414
  catch (error) {
415
+ if (telemetry?.io) {
416
+ await reportException({
417
+ error,
418
+ context: { source: `mcp:${name}`, handled: true, fatal: false },
419
+ projectDir: telemetry.projectDir,
420
+ io: telemetry.io,
421
+ redactionSecrets: await collectTelemetryRedactionSecrets({
422
+ projectDir: telemetry.projectDir,
423
+ includeLlm: true,
424
+ includeEmbeddings: true,
425
+ env: process.env,
426
+ }),
427
+ });
428
+ }
414
429
  return jsonErrorToolResult(formatToolError(error));
415
430
  }
416
431
  });
@@ -452,6 +467,20 @@ function instrumentMcpServer(server, telemetry) {
452
467
  return result;
453
468
  }
454
469
  catch (error) {
470
+ if (telemetry.io) {
471
+ await reportException({
472
+ error,
473
+ context: { source: `mcp:${name}`, handled: true, fatal: false },
474
+ projectDir: telemetry.projectDir,
475
+ io: telemetry.io,
476
+ redactionSecrets: await collectTelemetryRedactionSecrets({
477
+ projectDir: telemetry.projectDir,
478
+ includeLlm: true,
479
+ includeEmbeddings: true,
480
+ env: process.env,
481
+ }),
482
+ });
483
+ }
455
484
  if (telemetry.io && telemetry.projectDir && shouldEmitMcpTelemetry()) {
456
485
  const errorClass = scrubErrorClass(error);
457
486
  await emitTelemetryEvent({
@@ -476,6 +505,7 @@ function instrumentMcpServer(server, telemetry) {
476
505
  }
477
506
  export function registerKtxContextTools(deps) {
478
507
  const { ports, userContext } = deps;
508
+ const toolTelemetry = { projectDir: deps.projectDir, io: deps.io };
479
509
  const server = instrumentMcpServer(deps.server, {
480
510
  projectDir: deps.projectDir,
481
511
  io: deps.io,
@@ -489,7 +519,7 @@ export function registerKtxContextTools(deps) {
489
519
  inputSchema: connectionListSchema.shape,
490
520
  outputSchema: connectionListOutputSchema,
491
521
  annotations: toolAnnotations.connection_list,
492
- }, connectionListSchema, async () => jsonToolResult({ connections: await connections.list() }));
522
+ }, connectionListSchema, async () => jsonToolResult({ connections: await connections.list() }), toolTelemetry);
493
523
  }
494
524
  if (ports.knowledge) {
495
525
  const knowledge = ports.knowledge;
@@ -503,7 +533,7 @@ export function registerKtxContextTools(deps) {
503
533
  userId: userContext.userId,
504
534
  query: input.query,
505
535
  limit: input.limit,
506
- })));
536
+ })), toolTelemetry);
507
537
  registerParsedTool(server, 'wiki_read', {
508
538
  title: toolAnnotations.wiki_read.title,
509
539
  description: toolDescriptions.wiki_read,
@@ -513,7 +543,7 @@ export function registerKtxContextTools(deps) {
513
543
  }, knowledgeReadSchema, async (input) => {
514
544
  const page = await knowledge.read({ userId: userContext.userId, key: input.key });
515
545
  return page ? jsonToolResult(page) : jsonErrorToolResult(`Wiki page "${input.key}" was not found.`);
516
- });
546
+ }, toolTelemetry);
517
547
  }
518
548
  if (ports.semanticLayer) {
519
549
  const semanticLayer = ports.semanticLayer;
@@ -528,7 +558,7 @@ export function registerKtxContextTools(deps) {
528
558
  return source
529
559
  ? jsonToolResult(source)
530
560
  : jsonErrorToolResult(`Semantic-layer source "${input.sourceName}" was not found.`);
531
- });
561
+ }, toolTelemetry);
532
562
  registerParsedTool(server, 'sl_query', {
533
563
  title: toolAnnotations.sl_query.title,
534
564
  description: toolDescriptions.sl_query,
@@ -550,7 +580,7 @@ export function registerKtxContextTools(deps) {
550
580
  },
551
581
  }, onProgress ? { onProgress } : undefined);
552
582
  return jsonToolResult(projectSlQueryResult(result, input.include));
553
- });
583
+ }, toolTelemetry);
554
584
  }
555
585
  if (ports.entityDetails) {
556
586
  const entityDetails = ports.entityDetails;
@@ -560,7 +590,7 @@ export function registerKtxContextTools(deps) {
560
590
  inputSchema: entityDetailsSchema.shape,
561
591
  outputSchema: entityDetailsOutputSchema,
562
592
  annotations: toolAnnotations.entity_details,
563
- }, entityDetailsSchema, async (input) => jsonToolResult(await entityDetails.read(input)));
593
+ }, entityDetailsSchema, async (input) => jsonToolResult(await entityDetails.read(input)), toolTelemetry);
564
594
  }
565
595
  if (ports.dictionarySearch) {
566
596
  const dictionarySearch = ports.dictionarySearch;
@@ -570,7 +600,7 @@ export function registerKtxContextTools(deps) {
570
600
  inputSchema: dictionarySearchSchema.shape,
571
601
  outputSchema: dictionarySearchOutputSchema,
572
602
  annotations: toolAnnotations.dictionary_search,
573
- }, dictionarySearchSchema, async (input) => jsonToolResult(await dictionarySearch.search(input)));
603
+ }, dictionarySearchSchema, async (input) => jsonToolResult(await dictionarySearch.search(input)), toolTelemetry);
574
604
  }
575
605
  if (ports.discover) {
576
606
  const discover = ports.discover;
@@ -580,7 +610,7 @@ export function registerKtxContextTools(deps) {
580
610
  inputSchema: discoverDataSchema.shape,
581
611
  outputSchema: discoverDataOutputSchema,
582
612
  annotations: toolAnnotations.discover_data,
583
- }, discoverDataSchema, async (input) => jsonToolResult({ refs: await discover.search(input) }));
613
+ }, discoverDataSchema, async (input) => jsonToolResult({ refs: await discover.search(input) }), toolTelemetry);
584
614
  }
585
615
  if (ports.sqlExecution) {
586
616
  const sqlExecution = ports.sqlExecution;
@@ -597,7 +627,7 @@ export function registerKtxContextTools(deps) {
597
627
  sql: input.sql,
598
628
  maxRows: input.maxRows ?? 1000,
599
629
  }, onProgress ? { onProgress } : undefined));
600
- });
630
+ }, toolTelemetry);
601
631
  }
602
632
  if (ports.memoryIngest) {
603
633
  const memoryIngest = ports.memoryIngest;
@@ -617,7 +647,7 @@ export function registerKtxContextTools(deps) {
617
647
  sourceType: 'external_ingest',
618
648
  };
619
649
  return jsonToolResult(await memoryIngest.ingest(ingestInput));
620
- });
650
+ }, toolTelemetry);
621
651
  registerParsedTool(server, 'memory_ingest_status', {
622
652
  title: toolAnnotations.memory_ingest_status.title,
623
653
  description: toolDescriptions.memory_ingest_status,
@@ -627,6 +657,6 @@ export function registerKtxContextTools(deps) {
627
657
  }, memoryIngestStatusSchema, async (input) => {
628
658
  const status = await memoryIngest.status(input.runId);
629
659
  return status ? jsonToolResult(status) : jsonErrorToolResult(`Memory ingest run "${input.runId}" was not found.`);
630
- });
660
+ }, toolTelemetry);
631
661
  }
632
662
  }
@@ -6,6 +6,7 @@ declare const llmSchema: z.ZodObject<{
6
6
  vertex: "vertex";
7
7
  gateway: "gateway";
8
8
  "claude-code": "claude-code";
9
+ codex: "codex";
9
10
  none: "none";
10
11
  }>>;
11
12
  vertex: z.ZodOptional<z.ZodObject<{
@@ -327,6 +328,7 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
327
328
  vertex: "vertex";
328
329
  gateway: "gateway";
329
330
  "claude-code": "claude-code";
331
+ codex: "codex";
330
332
  none: "none";
331
333
  }>>;
332
334
  vertex: z.ZodOptional<z.ZodObject<{
@@ -395,6 +397,18 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
395
397
  continue: "continue";
396
398
  }>>;
397
399
  }, z.core.$strict>>;
400
+ rateLimit: z.ZodPrefault<z.ZodObject<{
401
+ enabled: z.ZodDefault<z.ZodBoolean>;
402
+ throttleThreshold: z.ZodDefault<z.ZodNumber>;
403
+ minConcurrencyUnderPressure: z.ZodDefault<z.ZodInt>;
404
+ maxWaitMs: z.ZodOptional<z.ZodInt>;
405
+ retry: z.ZodPrefault<z.ZodObject<{
406
+ maxAttempts: z.ZodDefault<z.ZodInt>;
407
+ baseDelayMs: z.ZodDefault<z.ZodInt>;
408
+ maxDelayMs: z.ZodDefault<z.ZodInt>;
409
+ jitter: z.ZodDefault<z.ZodBoolean>;
410
+ }, z.core.$strict>>;
411
+ }, z.core.$strict>>;
398
412
  profile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"json">]>>;
399
413
  }, z.core.$strict>>;
400
414
  agent: z.ZodPrefault<z.ZodObject<{
@@ -2,7 +2,7 @@ import { KTX_MODEL_ROLES } from '../../llm/types.js';
2
2
  import YAML from 'yaml';
3
3
  import * as z from 'zod';
4
4
  import { connectionConfigSchema } from './driver-schemas.js';
5
- const KTX_LLM_BACKENDS = ['none', 'anthropic', 'vertex', 'gateway', 'claude-code'];
5
+ const KTX_LLM_BACKENDS = ['none', 'anthropic', 'vertex', 'gateway', 'claude-code', 'codex'];
6
6
  const KTX_EMBEDDING_BACKENDS = ['none', 'openai', 'sentence-transformers'];
7
7
  const KTX_PROMPT_CACHE_TTLS = ['5m', '1h'];
8
8
  const KTX_ENRICHMENT_MODES = ['none', 'deterministic', 'llm'];
@@ -32,7 +32,7 @@ const llmProviderSchema = z
32
32
  backend: z
33
33
  .enum(KTX_LLM_BACKENDS)
34
34
  .default('none')
35
- .describe('LLM provider backend. "none" disables LLM features; "anthropic" / "vertex" / "gateway" require the matching nested credentials block; "claude-code" uses the local Claude Code session.'),
35
+ .describe('LLM provider backend. "none" disables LLM features; "anthropic" / "vertex" / "gateway" require the matching nested credentials block; "claude-code" uses the local Claude Code session; "codex" uses the local Codex session.'),
36
36
  vertex: vertexProviderSchema.optional().describe('Vertex AI credentials, used when backend is "vertex".'),
37
37
  anthropic: apiCredentialsSchema.optional().describe('Anthropic API credentials, used when backend is "anthropic".'),
38
38
  gateway: apiCredentialsSchema.optional().describe('AI Gateway credentials, used when backend is "gateway".'),
@@ -86,6 +86,40 @@ const workUnitsSchema = z
86
86
  .describe('Behavior when a work unit fails: "abort" stops the whole ingest run; "continue" records the failure and keeps going.'),
87
87
  })
88
88
  .describe('Concurrency and failure handling for ingest work units.');
89
+ const ingestRateLimitRetrySchema = z
90
+ .strictObject({
91
+ maxAttempts: z
92
+ .int()
93
+ .positive()
94
+ .default(6)
95
+ .describe('Maximum attempts for a single rate-limited LLM call before the failure surfaces, counting the first try. Also bounds how far opaque backoff grows for providers that do not expose a reset time.'),
96
+ baseDelayMs: z.int().positive().default(1_000).describe('Initial opaque retry delay in milliseconds.'),
97
+ maxDelayMs: z.int().positive().default(60_000).describe('Maximum opaque retry delay in milliseconds.'),
98
+ jitter: z.boolean().default(true).describe('When true, apply bounded jitter to opaque retry delays.'),
99
+ })
100
+ .describe('Retry policy for rate-limit responses that do not include a reset time or retry-after value.');
101
+ const ingestRateLimitSchema = z
102
+ .strictObject({
103
+ enabled: z.boolean().default(true).describe('Master switch for ingest LLM rate-limit pacing and visible waits.'),
104
+ throttleThreshold: z
105
+ .number()
106
+ .min(0)
107
+ .max(1)
108
+ .default(0.8)
109
+ .describe('Provider utilization at or above which ingest throttles new work-unit starts.'),
110
+ minConcurrencyUnderPressure: z
111
+ .int()
112
+ .positive()
113
+ .default(1)
114
+ .describe('Effective work-unit concurrency while a provider is under rate-limit pressure.'),
115
+ maxWaitMs: z
116
+ .int()
117
+ .positive()
118
+ .optional()
119
+ .describe('Optional cap on a single provider reset wait. Omit to wait indefinitely until the provider reset time.'),
120
+ retry: ingestRateLimitRetrySchema.prefault({}).describe('Opaque retry policy for providers without reset hints.'),
121
+ })
122
+ .describe('Rate-limit pacing and wait policy for ingest LLM calls.');
89
123
  const ingestSchema = z
90
124
  .strictObject({
91
125
  adapters: z
@@ -96,6 +130,7 @@ const ingestSchema = z
96
130
  .prefault({ backend: 'none' })
97
131
  .describe('Embedding configuration used when ingest adapters need to embed documents.'),
98
132
  workUnits: workUnitsSchema.prefault({}).describe('Concurrency and failure handling for ingest work units.'),
133
+ rateLimit: ingestRateLimitSchema.prefault({}).describe('LLM rate-limit pacing and visible-wait policy for ingest.'),
99
134
  profile: z
100
135
  .union([z.boolean(), z.literal('json')])
101
136
  .default(false)
@@ -233,10 +233,23 @@ export interface KtxTableListEntry {
233
233
  name: string;
234
234
  kind: 'table' | 'view';
235
235
  }
236
- interface KtxConnectorTestResult {
236
+ export interface KtxConnectorTestResult {
237
237
  success: boolean;
238
238
  error?: string;
239
- }
239
+ /**
240
+ * The original error thrown by the driver, preserved unflattened so the
241
+ * connection-test path can re-throw it. Keeping the real error object lets
242
+ * telemetry record the driver's actual error class (e.g. `ConnectionError`)
243
+ * and `.code` (e.g. `ELOGIN`) instead of collapsing every failure to `Error`.
244
+ */
245
+ cause?: unknown;
246
+ }
247
+ /**
248
+ * Single source of truth for a failed connector test result. Captures the
249
+ * driver's message for display while preserving the original error as `cause`
250
+ * so callers can surface its real class and code.
251
+ */
252
+ export declare function connectorTestFailure(error: unknown): KtxConnectorTestResult;
240
253
  export interface KtxScanConnector {
241
254
  id: string;
242
255
  driver: KtxConnectionDriver;
@@ -11,3 +11,15 @@ export function createKtxConnectorCapabilities(capabilities = {}) {
11
11
  estimatedRowCounts: capabilities.estimatedRowCounts ?? false,
12
12
  };
13
13
  }
14
+ /**
15
+ * Single source of truth for a failed connector test result. Captures the
16
+ * driver's message for display while preserving the original error as `cause`
17
+ * so callers can surface its real class and code.
18
+ */
19
+ export function connectorTestFailure(error) {
20
+ return {
21
+ success: false,
22
+ error: error instanceof Error ? error.message : String(error),
23
+ cause: error,
24
+ };
25
+ }
@@ -39,12 +39,6 @@ function humanizeIdentifier(value) {
39
39
  .trim()
40
40
  .toLowerCase();
41
41
  }
42
- function formatCount(count, singular, plural = `${singular}s`) {
43
- if (count <= 0) {
44
- return null;
45
- }
46
- return `${count} ${count === 1 ? singular : plural}`;
47
- }
48
42
  function sourceFallback(source, sourceName) {
49
43
  const table = cleanText(source.table);
50
44
  const sql = cleanText(source.sql);
@@ -54,14 +48,10 @@ function sourceFallback(source, sourceName) {
54
48
  if (sql) {
55
49
  return `Semantic-layer source for ${sourceName} backed by curated SQL.`;
56
50
  }
57
- const counts = [
58
- formatCount(Array.isArray(source.measures) ? source.measures.length : 0, 'measure'),
59
- formatCount(Array.isArray(source.segments) ? source.segments.length : 0, 'segment'),
60
- formatCount(Array.isArray(source.columns) ? source.columns.length : 0, 'computed column'),
61
- ].filter((item) => Boolean(item));
62
- return counts.length > 0
63
- ? `Semantic-layer overlay for ${sourceName} defining ${counts.join(', ')}.`
64
- : `Semantic-layer overlay for ${sourceName}.`;
51
+ // Measure/segment/column counts are rendered live from the body at list/read
52
+ // time, so baking them into stored prose freezes a derived value that drifts
53
+ // as the source later gains measures. Keep the auto fallback count-free.
54
+ return `Semantic-layer overlay for ${sourceName}.`;
65
55
  }
66
56
  function columnFallback(column, sourceName) {
67
57
  const columnName = cleanText(column.name) ?? 'column';