@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.
- package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/clack.d.ts +6 -0
- package/dist/clack.js +17 -2
- package/dist/cli-program.d.ts +3 -0
- package/dist/cli-program.js +42 -2
- package/dist/cli-runtime.d.ts +3 -0
- package/dist/cli-runtime.js +94 -3
- package/dist/commands/setup-commands.js +3 -4
- package/dist/connection-recovery.d.ts +34 -0
- package/dist/connection-recovery.js +82 -0
- package/dist/connection.js +26 -2
- package/dist/connectors/bigquery/connector.d.ts +2 -5
- package/dist/connectors/bigquery/connector.js +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +2 -5
- package/dist/connectors/clickhouse/connector.js +2 -2
- package/dist/connectors/mysql/connector.d.ts +7 -6
- package/dist/connectors/mysql/connector.js +25 -5
- package/dist/connectors/mysql/dialect.d.ts +1 -1
- package/dist/connectors/mysql/dialect.js +12 -2
- package/dist/connectors/postgres/connector.d.ts +2 -5
- package/dist/connectors/postgres/connector.js +2 -2
- package/dist/connectors/snowflake/connector.d.ts +2 -5
- package/dist/connectors/snowflake/connector.js +2 -2
- package/dist/connectors/sqlite/connector.d.ts +2 -5
- package/dist/connectors/sqlite/connector.js +2 -2
- package/dist/connectors/sqlserver/connector.d.ts +2 -5
- package/dist/connectors/sqlserver/connector.js +2 -2
- package/dist/context/connections/drivers.d.ts +0 -1
- package/dist/context/connections/drivers.js +0 -7
- package/dist/context/connections/query-executor.d.ts +2 -1
- package/dist/context/core/abort.d.ts +9 -0
- package/dist/context/core/abort.js +36 -0
- package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
- package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
- package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +30 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +194 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
- package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
- package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
- package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
- package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
- package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
- package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
- package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
- package/dist/context/ingest/final-gate-repair.d.ts +1 -0
- package/dist/context/ingest/final-gate-repair.js +1 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +127 -53
- package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +13 -5
- package/dist/context/ingest/local-ingest.d.ts +1 -0
- package/dist/context/ingest/local-ingest.js +13 -3
- package/dist/context/ingest/memory-flow/events.js +1 -1
- package/dist/context/ingest/memory-flow/schema.js +8 -3
- package/dist/context/ingest/memory-flow/types.d.ts +7 -3
- package/dist/context/ingest/ports.d.ts +3 -5
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
- package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/types.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
- package/dist/context/llm/ai-sdk-runtime.js +152 -16
- package/dist/context/llm/claude-code-runtime.d.ts +6 -4
- package/dist/context/llm/claude-code-runtime.js +127 -48
- package/dist/context/llm/codex-exec-events.d.ts +20 -0
- package/dist/context/llm/codex-exec-events.js +155 -0
- package/dist/context/llm/codex-isolation.d.ts +3 -0
- package/dist/context/llm/codex-isolation.js +5 -0
- package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
- package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
- package/dist/context/llm/codex-models.d.ts +2 -0
- package/dist/context/llm/codex-models.js +17 -0
- package/dist/context/llm/codex-runtime-config.d.ts +16 -0
- package/dist/context/llm/codex-runtime-config.js +19 -0
- package/dist/context/llm/codex-runtime.d.ts +37 -0
- package/dist/context/llm/codex-runtime.js +347 -0
- package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
- package/dist/context/llm/codex-sdk-runner.js +63 -0
- package/dist/context/llm/local-config.d.ts +16 -4
- package/dist/context/llm/local-config.js +18 -2
- package/dist/context/llm/rate-limit-governor.d.ts +103 -0
- package/dist/context/llm/rate-limit-governor.js +285 -0
- package/dist/context/llm/runtime-port.d.ts +3 -6
- package/dist/context/mcp/context-tools.js +43 -13
- package/dist/context/project/config.d.ts +14 -0
- package/dist/context/project/config.js +37 -2
- package/dist/context/scan/types.d.ts +15 -2
- package/dist/context/scan/types.js +12 -0
- package/dist/context/sl/description-normalization.js +4 -14
- package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
- package/dist/context/sql-analysis/ports.d.ts +12 -2
- package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
- package/dist/context-build-view.d.ts +13 -0
- package/dist/context-build-view.js +63 -32
- package/dist/demo-metrics.d.ts +0 -2
- package/dist/demo-metrics.js +1 -11
- package/dist/ingest.d.ts +1 -0
- package/dist/ingest.js +32 -3
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -0
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/llm/types.d.ts +1 -1
- package/dist/local-adapters.d.ts +10 -2
- package/dist/local-adapters.js +19 -3
- package/dist/memory-flow-hud.js +8 -16
- package/dist/next-steps.js +1 -2
- package/dist/progress-port-adapter.d.ts +6 -0
- package/dist/progress-port-adapter.js +18 -0
- package/dist/public-ingest.d.ts +20 -1
- package/dist/public-ingest.js +228 -42
- package/dist/reveal-password-prompt.d.ts +24 -0
- package/dist/reveal-password-prompt.js +78 -0
- package/dist/scan.js +21 -3
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +133 -27
- package/dist/setup-databases.d.ts +18 -1
- package/dist/setup-databases.js +378 -249
- package/dist/setup-demo-tour.js +1 -0
- package/dist/setup-embeddings.js +1 -1
- package/dist/setup-models.d.ts +11 -15
- package/dist/setup-models.js +140 -276
- package/dist/setup-prompts.js +3 -2
- package/dist/setup-ready-menu.d.ts +16 -2
- package/dist/setup-ready-menu.js +37 -5
- package/dist/setup-sources.js +115 -35
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +23 -11
- package/dist/sl.d.ts +2 -2
- package/dist/sl.js +20 -4
- package/dist/sql.js +18 -2
- package/dist/star-prompt/cache.d.ts +16 -0
- package/dist/star-prompt/cache.js +45 -0
- package/dist/star-prompt/star-count.d.ts +7 -0
- package/dist/star-prompt/star-count.js +66 -0
- package/dist/star-prompt/star-line.d.ts +12 -0
- package/dist/star-prompt/star-line.js +26 -0
- package/dist/status-project.d.ts +11 -0
- package/dist/status-project.js +50 -1
- package/dist/telemetry/command-hook.d.ts +1 -0
- package/dist/telemetry/command-hook.js +3 -1
- package/dist/telemetry/emitter.d.ts +10 -0
- package/dist/telemetry/emitter.js +31 -0
- package/dist/telemetry/events.d.ts +35 -6
- package/dist/telemetry/events.js +25 -2
- package/dist/telemetry/exception.d.ts +18 -0
- package/dist/telemetry/exception.js +162 -0
- package/dist/telemetry/identity.d.ts +0 -1
- package/dist/telemetry/identity.js +6 -6
- package/dist/telemetry/index.d.ts +15 -2
- package/dist/telemetry/index.js +15 -3
- package/dist/telemetry/redaction-secrets.d.ts +11 -0
- package/dist/telemetry/redaction-secrets.js +92 -0
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/dist/update-check/cache.d.ts +21 -0
- package/dist/update-check/cache.js +38 -0
- package/dist/update-check/channel.d.ts +15 -0
- package/dist/update-check/channel.js +30 -0
- package/dist/update-check/registry.d.ts +1 -0
- package/dist/update-check/registry.js +45 -0
- package/dist/update-check/update-check.d.ts +43 -0
- package/dist/update-check/update-check.js +116 -0
- package/package.json +12 -4
- package/dist/context/connections/local-query-executor.d.ts +0 -6
- package/dist/context/connections/local-query-executor.js +0 -39
- package/dist/context/connections/postgres-query-executor.d.ts +0 -25
- package/dist/context/connections/postgres-query-executor.js +0 -53
- package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
- 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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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';
|