@kaelio/ktx 0.7.0 → 0.9.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.7.0-py3-none-any.whl → kaelio_ktx-0.9.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +7 -0
- package/dist/cli-runtime.js +50 -3
- package/dist/command-schemas.d.ts +1 -1
- package/dist/command-tree.js +5 -1
- package/dist/commands/completion-commands.d.ts +3 -0
- package/dist/commands/completion-commands.js +38 -0
- package/dist/commands/ingest-commands.js +0 -4
- package/dist/commands/knowledge-commands.js +15 -2
- package/dist/commands/setup-commands.js +3 -3
- package/dist/commands/sl-commands.js +19 -7
- package/dist/completion/complete-engine.d.ts +19 -0
- package/dist/completion/complete-engine.js +128 -0
- package/dist/completion/completion-scripts.d.ts +1 -0
- package/dist/completion/completion-scripts.js +36 -0
- package/dist/completion/dynamic-candidates.d.ts +6 -0
- package/dist/completion/dynamic-candidates.js +98 -0
- package/dist/connection-drivers.d.ts +3 -0
- package/dist/connection-drivers.js +17 -0
- package/dist/connection-recovery.d.ts +34 -0
- package/dist/connection-recovery.js +82 -0
- package/dist/connection.js +3 -1
- 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 +29 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +190 -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-evidence/sqlite-context-evidence-store.d.ts +1 -1
- package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
- package/dist/context/ingest/ingest-bundle.runner.js +72 -15
- package/dist/context/ingest/ingest-profile.d.ts +102 -0
- package/dist/context/ingest/ingest-profile.js +306 -0
- package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +4 -2
- package/dist/context/ingest/local-ingest.d.ts +1 -1
- package/dist/context/ingest/local-ingest.js +6 -4
- package/dist/context/ingest/memory-flow/events.js +2 -1
- package/dist/context/ingest/ports.d.ts +2 -0
- package/dist/context/ingest/reports.d.ts +3 -0
- package/dist/context/ingest/reports.js +10 -0
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
- package/dist/context/ingest/tools/tool-call-logger.js +36 -1
- package/dist/context/llm/ai-sdk-runtime.js +32 -3
- package/dist/context/llm/claude-code-runtime.js +35 -2
- 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 +304 -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 +2 -0
- package/dist/context/llm/local-config.js +12 -1
- package/dist/context/llm/runtime-port.d.ts +25 -0
- package/dist/context/mcp/context-tools.d.ts +2 -1
- package/dist/context/mcp/context-tools.js +82 -15
- package/dist/context/mcp/server.js +4 -0
- package/dist/context/mcp/types.d.ts +15 -1
- package/dist/context/project/config.d.ts +3 -0
- package/dist/context/project/config.js +6 -2
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/search/discover.js +4 -3
- package/dist/context/sl/local-sl.d.ts +15 -0
- package/dist/context/sl/local-sl.js +30 -0
- 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/wiki/local-knowledge.d.ts +10 -0
- package/dist/context/wiki/local-knowledge.js +22 -0
- package/dist/context-build-view.d.ts +0 -3
- package/dist/context-build-view.js +5 -39
- package/dist/ingest.js +7 -10
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -0
- package/dist/knowledge.d.ts +5 -0
- package/dist/knowledge.js +10 -1
- 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/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-copy.js +1 -1
- package/dist/public-ingest.d.ts +20 -8
- package/dist/public-ingest.js +198 -61
- package/dist/scan.js +3 -1
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +138 -64
- package/dist/setup-databases.d.ts +17 -1
- package/dist/setup-databases.js +366 -326
- package/dist/setup-models.d.ts +10 -1
- package/dist/setup-models.js +90 -2
- package/dist/setup-ready-menu.d.ts +16 -2
- package/dist/setup-ready-menu.js +37 -5
- package/dist/setup-sources.js +141 -33
- package/dist/setup.js +24 -12
- package/dist/skills/analytics/SKILL.md +6 -1
- package/dist/sl.d.ts +6 -1
- package/dist/sl.js +32 -8
- 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.js +1 -1
- package/dist/telemetry/events.d.ts +15 -9
- package/dist/telemetry/events.js +17 -5
- package/dist/telemetry/identity.d.ts +1 -2
- package/dist/telemetry/identity.js +13 -10
- package/dist/telemetry/index.d.ts +13 -1
- package/dist/telemetry/index.js +18 -3
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/package.json +20 -19
- package/dist/ingest-depth.d.ts +0 -8
- package/dist/ingest-depth.js +0 -56
- package/dist/setup-database-context-depth.d.ts +0 -23
- package/dist/setup-database-context-depth.js +0 -84
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Codex } from '@openai/codex-sdk';
|
|
2
|
+
const CODEX_ENV_ALLOWLIST = new Set([
|
|
3
|
+
'HOME',
|
|
4
|
+
'USERPROFILE',
|
|
5
|
+
'APPDATA',
|
|
6
|
+
'LOCALAPPDATA',
|
|
7
|
+
'XDG_CONFIG_HOME',
|
|
8
|
+
'CODEX_HOME',
|
|
9
|
+
'CODEX_API_KEY',
|
|
10
|
+
'OPENAI_API_KEY',
|
|
11
|
+
'PATH',
|
|
12
|
+
'Path',
|
|
13
|
+
'SYSTEMROOT',
|
|
14
|
+
'COMSPEC',
|
|
15
|
+
'TMPDIR',
|
|
16
|
+
'TMP',
|
|
17
|
+
'TEMP',
|
|
18
|
+
'SSL_CERT_FILE',
|
|
19
|
+
'SSL_CERT_DIR',
|
|
20
|
+
'NODE_EXTRA_CA_CERTS',
|
|
21
|
+
'HTTPS_PROXY',
|
|
22
|
+
'HTTP_PROXY',
|
|
23
|
+
'ALL_PROXY',
|
|
24
|
+
'NO_PROXY',
|
|
25
|
+
]);
|
|
26
|
+
function buildCodexSdkEnv(baseEnv, overrides) {
|
|
27
|
+
const env = {};
|
|
28
|
+
for (const key of CODEX_ENV_ALLOWLIST) {
|
|
29
|
+
const value = baseEnv[key];
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
env[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ...env, ...(overrides ?? {}) };
|
|
35
|
+
}
|
|
36
|
+
export class CodexSdkCliRunner {
|
|
37
|
+
options;
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.options = options;
|
|
40
|
+
}
|
|
41
|
+
async runStreamed(input) {
|
|
42
|
+
const CodexClass = Codex;
|
|
43
|
+
const codex = new CodexClass({
|
|
44
|
+
...(input.configOverrides ? { config: input.configOverrides } : {}),
|
|
45
|
+
env: buildCodexSdkEnv(this.options.envBase ?? process.env, input.env),
|
|
46
|
+
...(this.options.codexPathOverride ? { codexPathOverride: this.options.codexPathOverride } : {}),
|
|
47
|
+
});
|
|
48
|
+
const thread = codex.startThread({
|
|
49
|
+
workingDirectory: input.projectDir,
|
|
50
|
+
skipGitRepoCheck: true,
|
|
51
|
+
model: input.model,
|
|
52
|
+
sandboxMode: 'read-only',
|
|
53
|
+
webSearchMode: 'disabled',
|
|
54
|
+
approvalPolicy: 'never',
|
|
55
|
+
});
|
|
56
|
+
const turnOptions = {
|
|
57
|
+
...(input.outputSchema ? { outputSchema: input.outputSchema } : {}),
|
|
58
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
59
|
+
};
|
|
60
|
+
const streamed = await thread.runStreamed(input.prompt, Object.keys(turnOptions).length > 0 ? turnOptions : undefined);
|
|
61
|
+
return streamed.events;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -3,6 +3,7 @@ import { createKtxLlmProvider } from '../../llm/model-provider.js';
|
|
|
3
3
|
import type { KtxEmbeddingConfig, KtxEmbeddingProvider, KtxLlmConfig, KtxLlmProvider } from '../../llm/types.js';
|
|
4
4
|
import type { KtxProjectEmbeddingConfig, KtxProjectLlmConfig } from '../project/config.js';
|
|
5
5
|
import { ClaudeCodeKtxLlmRuntime } from './claude-code-runtime.js';
|
|
6
|
+
import { CodexKtxLlmRuntime } from './codex-runtime.js';
|
|
6
7
|
import type { KtxLlmRuntimePort } from './runtime-port.js';
|
|
7
8
|
interface LocalConfigDeps {
|
|
8
9
|
env?: NodeJS.ProcessEnv;
|
|
@@ -10,6 +11,7 @@ interface LocalConfigDeps {
|
|
|
10
11
|
createKtxLlmProvider?: typeof createKtxLlmProvider;
|
|
11
12
|
createKtxEmbeddingProvider?: typeof createKtxEmbeddingProvider;
|
|
12
13
|
createClaudeCodeRuntime?: (deps: ConstructorParameters<typeof ClaudeCodeKtxLlmRuntime>[0]) => KtxLlmRuntimePort;
|
|
14
|
+
createCodexRuntime?: (deps: ConstructorParameters<typeof CodexKtxLlmRuntime>[0]) => KtxLlmRuntimePort;
|
|
13
15
|
createAiSdkRuntime?: (deps: {
|
|
14
16
|
llmProvider: KtxLlmProvider;
|
|
15
17
|
}) => KtxLlmRuntimePort;
|
|
@@ -3,6 +3,7 @@ import { createKtxLlmProvider } from '../../llm/model-provider.js';
|
|
|
3
3
|
import { resolveKtxConfigReference } from '../core/config-reference.js';
|
|
4
4
|
import { AiSdkKtxLlmRuntime } from './ai-sdk-runtime.js';
|
|
5
5
|
import { ClaudeCodeKtxLlmRuntime } from './claude-code-runtime.js';
|
|
6
|
+
import { CodexKtxLlmRuntime } from './codex-runtime.js';
|
|
6
7
|
function resolveOptional(value, env) {
|
|
7
8
|
return resolveKtxConfigReference(value, env) || undefined;
|
|
8
9
|
}
|
|
@@ -70,7 +71,7 @@ export function resolveLocalKtxLlmConfig(config, env) {
|
|
|
70
71
|
/** @internal */
|
|
71
72
|
export function createLocalKtxLlmProviderFromConfig(config, deps = {}) {
|
|
72
73
|
const resolved = resolveLocalKtxLlmConfig(config, deps.env ?? process.env);
|
|
73
|
-
if (!resolved || resolved.backend === 'claude-code') {
|
|
74
|
+
if (!resolved || resolved.backend === 'claude-code' || resolved.backend === 'codex') {
|
|
74
75
|
return null;
|
|
75
76
|
}
|
|
76
77
|
return (deps.createKtxLlmProvider ?? createKtxLlmProvider)(resolved);
|
|
@@ -91,6 +92,16 @@ export function createLocalKtxLlmRuntimeFromConfig(config, deps = {}) {
|
|
|
91
92
|
env: deps.env,
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
if (resolved.backend === 'codex') {
|
|
96
|
+
const projectDir = deps.projectDir;
|
|
97
|
+
if (!projectDir) {
|
|
98
|
+
throw new Error('projectDir is required when creating the codex LLM runtime');
|
|
99
|
+
}
|
|
100
|
+
return (deps.createCodexRuntime ?? ((runtimeDeps) => new CodexKtxLlmRuntime(runtimeDeps)))({
|
|
101
|
+
projectDir,
|
|
102
|
+
modelSlots: resolved.modelSlots,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
94
105
|
const llmProvider = (deps.createKtxLlmProvider ?? createKtxLlmProvider)(resolved);
|
|
95
106
|
return (deps.createAiSdkRuntime ?? ((runtimeDeps) => new AiSdkKtxLlmRuntime(runtimeDeps)))({ llmProvider });
|
|
96
107
|
}
|
|
@@ -17,6 +17,22 @@ export interface RunLoopStepInfo {
|
|
|
17
17
|
stepIndex: number;
|
|
18
18
|
stepBudget: number;
|
|
19
19
|
}
|
|
20
|
+
export interface LlmTokenUsage {
|
|
21
|
+
inputTokens?: number;
|
|
22
|
+
outputTokens?: number;
|
|
23
|
+
totalTokens?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Timing and token metrics for a multi-step agent loop, used for ingest profiling. */
|
|
26
|
+
export interface RunLoopMetrics {
|
|
27
|
+
/** Wall-clock time around the whole `generateText` call, in milliseconds. */
|
|
28
|
+
totalMs: number;
|
|
29
|
+
/** Aggregate token usage across all steps. */
|
|
30
|
+
usage: LlmTokenUsage;
|
|
31
|
+
/** Number of agent steps (model round-trips) that actually ran. */
|
|
32
|
+
stepCount: number;
|
|
33
|
+
/** Wall-clock offset (ms from loop start) at which each step finished. */
|
|
34
|
+
stepBoundariesMs: number[];
|
|
35
|
+
}
|
|
20
36
|
export interface RunLoopParams {
|
|
21
37
|
modelRole: KtxModelRole;
|
|
22
38
|
systemPrompt: string;
|
|
@@ -29,6 +45,7 @@ export interface RunLoopParams {
|
|
|
29
45
|
export interface RunLoopResult {
|
|
30
46
|
stopReason: RunLoopStopReason;
|
|
31
47
|
error?: Error;
|
|
48
|
+
metrics?: RunLoopMetrics;
|
|
32
49
|
}
|
|
33
50
|
export interface KtxGenerateTextInput {
|
|
34
51
|
role: KtxModelRole;
|
|
@@ -36,6 +53,10 @@ export interface KtxGenerateTextInput {
|
|
|
36
53
|
system?: string;
|
|
37
54
|
tools?: KtxRuntimeToolSet;
|
|
38
55
|
temperature?: number;
|
|
56
|
+
onMetrics?: (metrics: {
|
|
57
|
+
totalMs: number;
|
|
58
|
+
usage: LlmTokenUsage;
|
|
59
|
+
}) => void;
|
|
39
60
|
}
|
|
40
61
|
export interface KtxGenerateObjectInput<TOutput, TSchema extends z.ZodType<TOutput>> {
|
|
41
62
|
role: KtxModelRole;
|
|
@@ -44,6 +65,10 @@ export interface KtxGenerateObjectInput<TOutput, TSchema extends z.ZodType<TOutp
|
|
|
44
65
|
tools?: KtxRuntimeToolSet;
|
|
45
66
|
temperature?: number;
|
|
46
67
|
schema: TSchema;
|
|
68
|
+
onMetrics?: (metrics: {
|
|
69
|
+
totalMs: number;
|
|
70
|
+
usage: LlmTokenUsage;
|
|
71
|
+
}) => void;
|
|
47
72
|
}
|
|
48
73
|
export interface KtxLlmRuntimePort {
|
|
49
74
|
generateText(input: KtxGenerateTextInput): Promise<string>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { KtxCliIo } from '../../cli-runtime.js';
|
|
2
|
-
import type { KtxMcpContextPorts, KtxMcpServerLike, KtxMcpToolResult, KtxMcpUserContext, NonArrayObject } from './types.js';
|
|
2
|
+
import type { KtxMcpClientInfo, KtxMcpContextPorts, KtxMcpServerLike, KtxMcpToolResult, KtxMcpUserContext, NonArrayObject } from './types.js';
|
|
3
3
|
export interface RegisterKtxContextToolsDeps {
|
|
4
4
|
server: KtxMcpServerLike;
|
|
5
5
|
ports: KtxMcpContextPorts;
|
|
6
6
|
userContext: KtxMcpUserContext;
|
|
7
7
|
projectDir?: string;
|
|
8
8
|
io?: KtxCliIo;
|
|
9
|
+
getClientInfo?: () => KtxMcpClientInfo | undefined;
|
|
9
10
|
}
|
|
10
11
|
/** @internal */
|
|
11
12
|
export declare function jsonToolResult<T extends NonArrayObject>(structuredContent: T): KtxMcpToolResult<T>;
|
|
@@ -30,7 +30,7 @@ const toolDescriptions = {
|
|
|
30
30
|
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"] }] }).',
|
|
31
31
|
dictionary_search: 'Search profile-sampled warehouse values to locate likely source columns for business values. Example: dictionary_search({ values: ["Acme Corp"], connectionId: "warehouse" }).',
|
|
32
32
|
sl_read_source: 'Read a semantic-layer YAML source by connection id and source name. Example: sl_read_source({ connectionId: "warehouse", sourceName: "orders" }).',
|
|
33
|
-
sl_query: 'Execute a semantic-layer query and return rows,
|
|
33
|
+
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"] }).',
|
|
34
34
|
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 }).',
|
|
35
35
|
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." }).',
|
|
36
36
|
memory_ingest_status: 'Read the current or final status for a memory ingest run. Example: memory_ingest_status({ runId: "memory-run-1" }).',
|
|
@@ -38,7 +38,7 @@ const toolDescriptions = {
|
|
|
38
38
|
const connectionListSchema = z.object({});
|
|
39
39
|
const knowledgeSearchSchema = z.object({
|
|
40
40
|
query: z.string().min(1).describe('Natural-language wiki search query, e.g. "revenue recognition policy".'),
|
|
41
|
-
limit: z.number().int().min(1).max(50).default(10).describe('Maximum wiki pages to return.
|
|
41
|
+
limit: z.number().int().min(1).max(50).default(10).describe('Maximum wiki pages to return.'),
|
|
42
42
|
});
|
|
43
43
|
const knowledgeReadSchema = z.object({
|
|
44
44
|
key: z.string().min(1).describe('Wiki page key returned by wiki_search, e.g. "global/revenue".'),
|
|
@@ -67,10 +67,7 @@ const slQueryOrderBySchema = z.object({
|
|
|
67
67
|
.string()
|
|
68
68
|
.min(1)
|
|
69
69
|
.describe('Field/measure/dimension id to order by, e.g. "orders.created_at", a dimension key like "mart_nrr_quarterly.quarter_label", or a measure alias.'),
|
|
70
|
-
direction: z
|
|
71
|
-
.enum(['asc', 'desc'])
|
|
72
|
-
.default('asc')
|
|
73
|
-
.describe('Sort direction: "asc" or "desc". Defaults to "asc".'),
|
|
70
|
+
direction: z.enum(['asc', 'desc']).default('asc').describe('Sort direction for this field.'),
|
|
74
71
|
});
|
|
75
72
|
const slQuerySchema = z.object({
|
|
76
73
|
connectionId: connectionIdSchema
|
|
@@ -93,8 +90,12 @@ const slQuerySchema = z.object({
|
|
|
93
90
|
.array(slQueryOrderBySchema)
|
|
94
91
|
.default([])
|
|
95
92
|
.describe('Sort clauses. Use {field, direction?} entries.'),
|
|
96
|
-
limit: z.number().int().min(0).default(1000).describe('Maximum rows to return.
|
|
97
|
-
include_empty: z.boolean().default(true).describe('Whether to include empty dimension groups.
|
|
93
|
+
limit: z.number().int().min(0).default(1000).describe('Maximum rows to return.'),
|
|
94
|
+
include_empty: z.boolean().default(true).describe('Whether to include empty dimension groups.'),
|
|
95
|
+
include: z
|
|
96
|
+
.array(z.enum(['plan', 'sql']))
|
|
97
|
+
.default([])
|
|
98
|
+
.describe('Extra detail to attach to the response: "sql" for the generated SQL, "plan" for the full query plan.'),
|
|
98
99
|
});
|
|
99
100
|
const entityDetailsTableRefSchema = z.object({
|
|
100
101
|
catalog: z.string().nullable().describe('Catalog/project/database. Use null when not applicable.'),
|
|
@@ -134,12 +135,12 @@ const discoverDataSchema = z.object({
|
|
|
134
135
|
.optional()
|
|
135
136
|
.describe('Optional connection id. Pass it when user intent pins a specific warehouse.'),
|
|
136
137
|
kinds: z.array(discoverDataKindSchema.describe('Reference kind to include.')).optional().describe('Optional kind filter.'),
|
|
137
|
-
limit: z.number().int().min(1).max(50).default(
|
|
138
|
+
limit: z.number().int().min(1).max(50).default(10).optional().describe('Maximum refs to return.'),
|
|
138
139
|
});
|
|
139
140
|
const sqlExecutionSchema = z.object({
|
|
140
141
|
connectionId: connectionIdSchema.describe('Connection id to execute against. Required for raw SQL.'),
|
|
141
142
|
sql: z.string().min(1).describe('Parser-validated read-only SQL, e.g. "select count(*) from public.orders".'),
|
|
142
|
-
maxRows: z.number().int().min(1).max(10_000).default(1000).optional().describe('Maximum rows to return.
|
|
143
|
+
maxRows: z.number().int().min(1).max(10_000).default(1000).optional().describe('Maximum rows to return.'),
|
|
143
144
|
});
|
|
144
145
|
const memoryIngestSchema = z.object({
|
|
145
146
|
content: z
|
|
@@ -198,10 +199,14 @@ const slReadSourceOutputSchema = z.object({
|
|
|
198
199
|
const slQueryOutputSchema = z.object({
|
|
199
200
|
connectionId: z.string().optional(),
|
|
200
201
|
dialect: z.string().optional(),
|
|
201
|
-
sql: z.string(),
|
|
202
202
|
headers: z.array(z.string()),
|
|
203
203
|
rows: z.array(z.array(z.unknown())),
|
|
204
204
|
totalRows: z.number(),
|
|
205
|
+
// Correctness signals hoisted out of `plan` so they survive default projection (e.g. compile-only
|
|
206
|
+
// status, fan-out warnings). Present only when there is something to report.
|
|
207
|
+
notes: z.array(z.string()).optional(),
|
|
208
|
+
// Opt-in detail, attached only when requested via the `include` input.
|
|
209
|
+
sql: z.string().optional(),
|
|
205
210
|
plan: unknownRecordSchema.optional(),
|
|
206
211
|
});
|
|
207
212
|
const entityDetailsSnapshotOutputSchema = z.object({
|
|
@@ -321,11 +326,54 @@ const memoryIngestStatusOutputSchema = z.object({
|
|
|
321
326
|
});
|
|
322
327
|
/** @internal */
|
|
323
328
|
export function jsonToolResult(structuredContent) {
|
|
329
|
+
// Compact (non-indented) JSON: this `content` text is the copy the model reads. Pretty-printing
|
|
330
|
+
// arrays-of-arrays (every `rows` payload) puts one scalar per line, inflating tabular results by
|
|
331
|
+
// a large constant factor. `structuredContent` carries the same data for structured-output clients.
|
|
324
332
|
return {
|
|
325
|
-
content: [{ type: 'text', text: JSON.stringify(structuredContent
|
|
333
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent) }],
|
|
326
334
|
structuredContent,
|
|
327
335
|
};
|
|
328
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Pull the correctness-critical signals out of a query plan so they survive even when the caller
|
|
339
|
+
* did not opt into the full `plan`. Returns an empty list when there is nothing to flag.
|
|
340
|
+
*/
|
|
341
|
+
function slQueryNotes(plan) {
|
|
342
|
+
if (!plan) {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
const notes = [];
|
|
346
|
+
const execution = plan.execution;
|
|
347
|
+
if (execution &&
|
|
348
|
+
typeof execution === 'object' &&
|
|
349
|
+
execution.mode === 'compile_only') {
|
|
350
|
+
const reason = execution.reason;
|
|
351
|
+
notes.push(typeof reason === 'string' ? reason : 'Compiled SQL only; no rows were executed.');
|
|
352
|
+
}
|
|
353
|
+
if (plan.has_fan_out === true) {
|
|
354
|
+
const description = typeof plan.fan_out_description === 'string' ? plan.fan_out_description.trim() : '';
|
|
355
|
+
notes.push(description.length > 0 ? description : 'Fan-out detected: measure totals may be inflated by joins.');
|
|
356
|
+
}
|
|
357
|
+
return notes;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Default sl_query response is the minimum the agent needs to read the result: connection, headers,
|
|
361
|
+
* rows, totals, plus any correctness notes. The generated `sql` and the full `plan` are attached only
|
|
362
|
+
* when explicitly requested via `include`, since both are large and echo information the caller already has.
|
|
363
|
+
*/
|
|
364
|
+
function projectSlQueryResult(result, include) {
|
|
365
|
+
const notes = slQueryNotes(result.plan);
|
|
366
|
+
return {
|
|
367
|
+
...(result.connectionId !== undefined ? { connectionId: result.connectionId } : {}),
|
|
368
|
+
...(result.dialect !== undefined ? { dialect: result.dialect } : {}),
|
|
369
|
+
headers: result.headers,
|
|
370
|
+
rows: result.rows,
|
|
371
|
+
totalRows: result.totalRows,
|
|
372
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
373
|
+
...(include.includes('sql') ? { sql: result.sql } : {}),
|
|
374
|
+
...(include.includes('plan') && result.plan ? { plan: result.plan } : {}),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
329
377
|
function jsonErrorToolResult(text) {
|
|
330
378
|
return {
|
|
331
379
|
content: [{ type: 'text', text }],
|
|
@@ -367,6 +415,18 @@ function registerParsedTool(server, name, config, schema, handler) {
|
|
|
367
415
|
}
|
|
368
416
|
});
|
|
369
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Resolves the connected client's identity into the raw telemetry fields. The
|
|
420
|
+
* strings are client-controlled and untrusted, so they only ever land in the
|
|
421
|
+
* telemetry property bag — never in paths, logs, or error messages.
|
|
422
|
+
*/
|
|
423
|
+
function clientTelemetryFields(getClientInfo) {
|
|
424
|
+
const client = getClientInfo?.();
|
|
425
|
+
return {
|
|
426
|
+
...(client?.name ? { mcpClientName: client.name } : {}),
|
|
427
|
+
...(client?.version ? { mcpClientVersion: client.version } : {}),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
370
430
|
function instrumentMcpServer(server, telemetry) {
|
|
371
431
|
return {
|
|
372
432
|
registerTool(name, config, handler) {
|
|
@@ -385,6 +445,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
385
445
|
outcome: isError ? 'error' : 'ok',
|
|
386
446
|
durationMs: Math.max(0, performance.now() - startedAt),
|
|
387
447
|
sampleRate: mcpTelemetrySampleRate(),
|
|
448
|
+
...clientTelemetryFields(telemetry.getClientInfo),
|
|
388
449
|
},
|
|
389
450
|
});
|
|
390
451
|
}
|
|
@@ -403,6 +464,7 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
403
464
|
...(errorClass ? { errorClass } : {}),
|
|
404
465
|
durationMs: Math.max(0, performance.now() - startedAt),
|
|
405
466
|
sampleRate: mcpTelemetrySampleRate(),
|
|
467
|
+
...clientTelemetryFields(telemetry.getClientInfo),
|
|
406
468
|
},
|
|
407
469
|
});
|
|
408
470
|
}
|
|
@@ -414,7 +476,11 @@ function instrumentMcpServer(server, telemetry) {
|
|
|
414
476
|
}
|
|
415
477
|
export function registerKtxContextTools(deps) {
|
|
416
478
|
const { ports, userContext } = deps;
|
|
417
|
-
const server = instrumentMcpServer(deps.server, {
|
|
479
|
+
const server = instrumentMcpServer(deps.server, {
|
|
480
|
+
projectDir: deps.projectDir,
|
|
481
|
+
io: deps.io,
|
|
482
|
+
getClientInfo: deps.getClientInfo,
|
|
483
|
+
});
|
|
418
484
|
if (ports.connections) {
|
|
419
485
|
const connections = ports.connections;
|
|
420
486
|
registerParsedTool(server, 'connection_list', {
|
|
@@ -471,7 +537,7 @@ export function registerKtxContextTools(deps) {
|
|
|
471
537
|
annotations: toolAnnotations.sl_query,
|
|
472
538
|
}, slQuerySchema, async (input, context) => {
|
|
473
539
|
const onProgress = mcpProgressCallback(context);
|
|
474
|
-
|
|
540
|
+
const result = await semanticLayer.query({
|
|
475
541
|
connectionId: input.connectionId,
|
|
476
542
|
query: {
|
|
477
543
|
measures: input.measures,
|
|
@@ -482,7 +548,8 @@ export function registerKtxContextTools(deps) {
|
|
|
482
548
|
limit: input.limit,
|
|
483
549
|
include_empty: input.include_empty,
|
|
484
550
|
},
|
|
485
|
-
}, onProgress ? { onProgress } : undefined)
|
|
551
|
+
}, onProgress ? { onProgress } : undefined);
|
|
552
|
+
return jsonToolResult(projectSlQueryResult(result, input.include));
|
|
486
553
|
});
|
|
487
554
|
}
|
|
488
555
|
if (ports.entityDetails) {
|
|
@@ -9,6 +9,7 @@ export function createKtxMcpServer(deps) {
|
|
|
9
9
|
userContext: deps.userContext,
|
|
10
10
|
projectDir: deps.projectDir,
|
|
11
11
|
io: deps.io,
|
|
12
|
+
getClientInfo: deps.getClientInfo,
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
return deps.server;
|
|
@@ -24,6 +25,9 @@ export function createDefaultKtxMcpServer(deps) {
|
|
|
24
25
|
contextTools: deps.contextTools,
|
|
25
26
|
projectDir: deps.projectDir,
|
|
26
27
|
io: deps.io,
|
|
28
|
+
// The SDK populates the client identity after the initialize handshake, so
|
|
29
|
+
// read it lazily at emit time rather than at registration (undefined here).
|
|
30
|
+
getClientInfo: () => server.server.getClientVersion(),
|
|
27
31
|
});
|
|
28
32
|
return server;
|
|
29
33
|
}
|
|
@@ -46,6 +46,15 @@ export interface MemoryIngestPort {
|
|
|
46
46
|
export interface KtxMcpUserContext {
|
|
47
47
|
userId: string;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Identity of the connected MCP client tool (e.g. Claude Desktop, Cursor),
|
|
51
|
+
* read from the initialize handshake. Untrusted, client-controlled strings —
|
|
52
|
+
* use only as telemetry properties, never to build paths or log lines.
|
|
53
|
+
*/
|
|
54
|
+
export interface KtxMcpClientInfo {
|
|
55
|
+
name: string;
|
|
56
|
+
version: string;
|
|
57
|
+
}
|
|
49
58
|
export interface KtxMcpServerLike {
|
|
50
59
|
registerTool(name: string, config: {
|
|
51
60
|
title?: string;
|
|
@@ -101,7 +110,10 @@ interface KtxSemanticLayerReadResponse {
|
|
|
101
110
|
sourceName: string;
|
|
102
111
|
yaml: string;
|
|
103
112
|
}
|
|
104
|
-
|
|
113
|
+
/** @internal */
|
|
114
|
+
export interface KtxSemanticLayerQueryResponse {
|
|
115
|
+
connectionId?: string;
|
|
116
|
+
dialect?: string;
|
|
105
117
|
sql: string;
|
|
106
118
|
headers: string[];
|
|
107
119
|
rows: unknown[][];
|
|
@@ -165,5 +177,7 @@ export interface KtxMcpServerDeps {
|
|
|
165
177
|
contextTools?: KtxMcpContextPorts;
|
|
166
178
|
projectDir?: string;
|
|
167
179
|
io?: KtxCliIo;
|
|
180
|
+
/** Reads the connected client's identity once the initialize handshake completes. */
|
|
181
|
+
getClientInfo?: () => KtxMcpClientInfo | undefined;
|
|
168
182
|
}
|
|
169
183
|
export {};
|
|
@@ -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,7 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
395
397
|
continue: "continue";
|
|
396
398
|
}>>;
|
|
397
399
|
}, z.core.$strict>>;
|
|
400
|
+
profile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"json">]>>;
|
|
398
401
|
}, z.core.$strict>>;
|
|
399
402
|
agent: z.ZodPrefault<z.ZodObject<{
|
|
400
403
|
run_research: 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".'),
|
|
@@ -96,6 +96,10 @@ const ingestSchema = z
|
|
|
96
96
|
.prefault({ backend: 'none' })
|
|
97
97
|
.describe('Embedding configuration used when ingest adapters need to embed documents.'),
|
|
98
98
|
workUnits: workUnitsSchema.prefault({}).describe('Concurrency and failure handling for ingest work units.'),
|
|
99
|
+
profile: z
|
|
100
|
+
.union([z.boolean(), z.literal('json')])
|
|
101
|
+
.default(false)
|
|
102
|
+
.describe('Print a timing breakdown to stderr at the end of each ingest run. `true` prints a human table; `"json"` prints the raw structured profile for coding agents; `false` disables it. Equivalent to the KTX_PROFILE_INGEST environment variable (`1`/`true`/`json`).'),
|
|
99
103
|
})
|
|
100
104
|
.describe('Ingest pipeline configuration: adapters, embeddings, and work-unit policy.');
|
|
101
105
|
const scanEnrichmentSchema = z
|
|
@@ -21,7 +21,7 @@ function warehouseConnectionSchema(driver) {
|
|
|
21
21
|
enabled_tables: z
|
|
22
22
|
.array(z.string().min(1))
|
|
23
23
|
.optional()
|
|
24
|
-
.describe('Optional allowlist of fully-qualified table names ("schema.table") to ingest. When set, live-database ingest discards any table whose schema-qualified name is not in this list. Useful for smoke-testing
|
|
24
|
+
.describe('Optional allowlist of fully-qualified table names ("schema.table") to ingest. When set, live-database ingest discards any table whose schema-qualified name is not in this list. Useful for smoke-testing ingest on a single table.'),
|
|
25
25
|
})
|
|
26
26
|
.describe(`${driver} warehouse connection. Additional driver-tunable fields (e.g. context.queryHistory) are accepted and passed through.`);
|
|
27
27
|
}
|
|
@@ -95,7 +95,7 @@ async function wikiCandidates(project, input, options, terms) {
|
|
|
95
95
|
query: input.query,
|
|
96
96
|
userId: options.userId,
|
|
97
97
|
embeddingService: options.embeddingService ?? null,
|
|
98
|
-
limit: Math.max(input.limit ??
|
|
98
|
+
limit: Math.max(input.limit ?? 10, 25),
|
|
99
99
|
});
|
|
100
100
|
const records = [];
|
|
101
101
|
for (const result of searchResults) {
|
|
@@ -300,7 +300,8 @@ function hydrate(fused, refsByKey) {
|
|
|
300
300
|
}
|
|
301
301
|
return {
|
|
302
302
|
...ref,
|
|
303
|
-
|
|
303
|
+
// 3 decimals is plenty for a relative-rank hint; 6 just spent bytes on noise.
|
|
304
|
+
score: maxScore > 0 ? Number((candidate.score / maxScore).toFixed(3)) : 0,
|
|
304
305
|
};
|
|
305
306
|
})
|
|
306
307
|
.filter((result) => result !== null);
|
|
@@ -308,7 +309,7 @@ function hydrate(fused, refsByKey) {
|
|
|
308
309
|
export function createKtxDiscoverDataService(project, options = {}) {
|
|
309
310
|
return {
|
|
310
311
|
async search(input) {
|
|
311
|
-
const limit = Math.max(1, Math.min(input.limit ??
|
|
312
|
+
const limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
312
313
|
const query = input.query.trim();
|
|
313
314
|
if (!query) {
|
|
314
315
|
return [];
|
|
@@ -28,6 +28,7 @@ export interface LocalSlSearchInput {
|
|
|
28
28
|
backend?: 'pglite-owner-prototype';
|
|
29
29
|
pglite?: PgliteSlSearchPrototypeOwnerOptions;
|
|
30
30
|
}
|
|
31
|
+
/** @internal */
|
|
31
32
|
export interface LocalSlSource extends LocalSlSourceSummary {
|
|
32
33
|
yaml: string;
|
|
33
34
|
}
|
|
@@ -38,6 +39,15 @@ export interface LocalSlValidationResult {
|
|
|
38
39
|
valid: boolean;
|
|
39
40
|
errors: string[];
|
|
40
41
|
}
|
|
42
|
+
export type ResolvedSlSource = {
|
|
43
|
+
kind: 'found';
|
|
44
|
+
source: LocalSlSource;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'not-found';
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'ambiguous';
|
|
49
|
+
connectionIds: string[];
|
|
50
|
+
};
|
|
41
51
|
export declare function loadLocalSlSourceRecords(project: KtxLocalProject, input: {
|
|
42
52
|
connectionId: string;
|
|
43
53
|
}): Promise<LocalSlSourceRecord[]>;
|
|
@@ -52,10 +62,15 @@ export declare function writeLocalSlSource(project: KtxLocalProject, input: {
|
|
|
52
62
|
sourceName: string;
|
|
53
63
|
yaml: string;
|
|
54
64
|
}): Promise<KtxFileWriteResult>;
|
|
65
|
+
/** @internal */
|
|
55
66
|
export declare function readLocalSlSource(project: KtxLocalProject, input: {
|
|
56
67
|
connectionId: string;
|
|
57
68
|
sourceName: string;
|
|
58
69
|
}): Promise<LocalSlSource | null>;
|
|
70
|
+
export declare function resolveLocalSlSource(project: KtxLocalProject, input: {
|
|
71
|
+
sourceName: string;
|
|
72
|
+
connectionId?: string;
|
|
73
|
+
}): Promise<ResolvedSlSource>;
|
|
59
74
|
export declare function listLocalSlSources(project: KtxLocalProject, input?: {
|
|
60
75
|
connectionId?: string;
|
|
61
76
|
}): Promise<LocalSlSourceSummary[]>;
|
|
@@ -204,6 +204,7 @@ export async function writeLocalSlSource(project, input) {
|
|
|
204
204
|
const path = slPath(input.connectionId, input.sourceName);
|
|
205
205
|
return project.fileStore.writeFile(path, input.yaml.endsWith('\n') ? input.yaml : `${input.yaml}\n`, LOCAL_AUTHOR, LOCAL_AUTHOR_EMAIL, `Write semantic-layer source: ${input.connectionId}/${input.sourceName}`);
|
|
206
206
|
}
|
|
207
|
+
/** @internal */
|
|
207
208
|
export async function readLocalSlSource(project, input) {
|
|
208
209
|
const path = slPath(input.connectionId, input.sourceName);
|
|
209
210
|
try {
|
|
@@ -221,6 +222,35 @@ export async function readLocalSlSource(project, input) {
|
|
|
221
222
|
return record ? { ...record } : null;
|
|
222
223
|
}
|
|
223
224
|
}
|
|
225
|
+
export async function resolveLocalSlSource(project, input) {
|
|
226
|
+
if (input.connectionId !== undefined) {
|
|
227
|
+
const source = await readLocalSlSource(project, {
|
|
228
|
+
connectionId: input.connectionId,
|
|
229
|
+
sourceName: input.sourceName,
|
|
230
|
+
});
|
|
231
|
+
return source ? { kind: 'found', source } : { kind: 'not-found' };
|
|
232
|
+
}
|
|
233
|
+
const summaries = await listLocalSlSources(project, {});
|
|
234
|
+
const matches = summaries.filter((summary) => summary.name === input.sourceName);
|
|
235
|
+
if (matches.length === 0) {
|
|
236
|
+
return { kind: 'not-found' };
|
|
237
|
+
}
|
|
238
|
+
if (matches.length > 1) {
|
|
239
|
+
return {
|
|
240
|
+
kind: 'ambiguous',
|
|
241
|
+
connectionIds: [...new Set(matches.map((match) => match.connectionId))].sort(),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const match = matches[0];
|
|
245
|
+
if (match === undefined) {
|
|
246
|
+
return { kind: 'not-found' };
|
|
247
|
+
}
|
|
248
|
+
const source = await readLocalSlSource(project, {
|
|
249
|
+
connectionId: match.connectionId,
|
|
250
|
+
sourceName: input.sourceName,
|
|
251
|
+
});
|
|
252
|
+
return source ? { kind: 'found', source } : { kind: 'not-found' };
|
|
253
|
+
}
|
|
224
254
|
export async function listLocalSlSources(project, input = {}) {
|
|
225
255
|
if (input.connectionId) {
|
|
226
256
|
return (await loadLocalSlSourceRecords(project, { connectionId: input.connectionId })).map(({ source: _source, yaml: _yaml, ...summary }) => summary);
|