@kaelio/ktx 0.8.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.8.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-runtime.js +50 -3
- package/dist/commands/setup-commands.js +1 -1
- 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/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +3 -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/project/config.d.ts +2 -0
- package/dist/context/project/config.js +2 -2
- 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.js +4 -32
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -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/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 +178 -27
- package/dist/scan.js +3 -1
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +133 -27
- package/dist/setup-databases.d.ts +17 -1
- package/dist/setup-databases.js +358 -249
- 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 +108 -28
- package/dist/setup.js +22 -10
- 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/events.d.ts +11 -6
- package/dist/telemetry/events.js +10 -2
- package/dist/telemetry/identity.d.ts +0 -1
- package/dist/telemetry/identity.js +6 -6
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.js +13 -2
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/package.json +5 -4
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { noopLogger } from '../core/config.js';
|
|
3
|
+
import { isCompletedAgentStep, summarizeCodexExecEvents } from './codex-exec-events.js';
|
|
4
|
+
import { startCodexRuntimeMcpServer, } from './codex-mcp-runtime-server.js';
|
|
5
|
+
import { resolveCodexModel } from './codex-models.js';
|
|
6
|
+
import { buildCodexRuntimeConfig } from './codex-runtime-config.js';
|
|
7
|
+
import { CodexSdkCliRunner } from './codex-sdk-runner.js';
|
|
8
|
+
function modelForRole(modelSlots, role) {
|
|
9
|
+
return resolveCodexModel(modelSlots[role] ?? modelSlots.default);
|
|
10
|
+
}
|
|
11
|
+
function promptWithSystem(system, prompt) {
|
|
12
|
+
return [system, prompt].filter(Boolean).join('\n\n');
|
|
13
|
+
}
|
|
14
|
+
function eventRecord(value) {
|
|
15
|
+
return value && typeof value === 'object' ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function isTurnCompleted(event) {
|
|
18
|
+
return eventRecord(event)?.type === 'turn.completed';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Drains the Codex stream once, emitting a step as each agent action completes
|
|
22
|
+
* so callers see live progress and the step budget is enforced mid-run. Every
|
|
23
|
+
* completed agent-action item counts (see {@link isCompletedAgentStep}), so
|
|
24
|
+
* built-in `command_execution` steps decrement the budget the same as
|
|
25
|
+
* `mcp_tool_call`s. A turn that produced no actions still counts as one step,
|
|
26
|
+
* matching the metrics summary and the AI SDK backend.
|
|
27
|
+
*/
|
|
28
|
+
async function collectEvents(events, options = {}) {
|
|
29
|
+
const collected = [];
|
|
30
|
+
let completedSteps = 0;
|
|
31
|
+
let sawActionStep = false;
|
|
32
|
+
let budgetExceeded = false;
|
|
33
|
+
let streamError;
|
|
34
|
+
// The SDK yields every stdout event, then throws on a non-zero codex exec
|
|
35
|
+
// exit. Catch that throw so the events already collected (which carry the
|
|
36
|
+
// real `turn.failed`/`error` reason) survive for the summary; the masked
|
|
37
|
+
// exit message is kept only as a fallback when no error event was emitted.
|
|
38
|
+
try {
|
|
39
|
+
for await (const event of events) {
|
|
40
|
+
collected.push(event);
|
|
41
|
+
const isActionStep = isCompletedAgentStep(event);
|
|
42
|
+
if (isActionStep) {
|
|
43
|
+
sawActionStep = true;
|
|
44
|
+
}
|
|
45
|
+
else if (sawActionStep || !isTurnCompleted(event)) {
|
|
46
|
+
// Only fall back to counting a bare turn as a step when the turn produced
|
|
47
|
+
// no agent actions; a completed turn is terminal, so it never aborts.
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
completedSteps += 1;
|
|
51
|
+
await options.onStep?.(completedSteps);
|
|
52
|
+
if (isActionStep && options.stepBudget !== undefined && completedSteps >= options.stepBudget) {
|
|
53
|
+
budgetExceeded = true;
|
|
54
|
+
options.abortController?.abort();
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
streamError = error instanceof Error ? error : new Error(String(error));
|
|
61
|
+
}
|
|
62
|
+
return { events: collected, budgetExceeded, ...(streamError ? { streamError } : {}) };
|
|
63
|
+
}
|
|
64
|
+
function metrics(summary, startedAt) {
|
|
65
|
+
return { totalMs: Date.now() - startedAt, usage: summary.usage };
|
|
66
|
+
}
|
|
67
|
+
function summaryError(summary, streamError) {
|
|
68
|
+
// A `turn.failed`/`error` event carries the real reason; prefer it over the
|
|
69
|
+
// SDK's generic non-zero-exit throw. Fall back to the stream error only when
|
|
70
|
+
// no event explained the failure (e.g. spawn failure or auth before a turn).
|
|
71
|
+
if (summary.error) {
|
|
72
|
+
return summary.error;
|
|
73
|
+
}
|
|
74
|
+
if (summary.toolFailures.length > 0) {
|
|
75
|
+
return new Error(`Codex runtime tool call failed: ${summary.toolFailures.join('; ')}`);
|
|
76
|
+
}
|
|
77
|
+
return streamError;
|
|
78
|
+
}
|
|
79
|
+
function assertSuccessfulText(summary, streamError) {
|
|
80
|
+
const error = summaryError(summary, streamError);
|
|
81
|
+
if (error) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
if (!summary.finalText.trim()) {
|
|
85
|
+
throw new Error('Codex completed without an agent message');
|
|
86
|
+
}
|
|
87
|
+
return summary.finalText;
|
|
88
|
+
}
|
|
89
|
+
function parseStructuredOutput(schema, text) {
|
|
90
|
+
try {
|
|
91
|
+
return schema.parse(JSON.parse(text));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
throw new Error(`Codex structured output failed validation: ${message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function mcpForTools(input) {
|
|
99
|
+
if (!input.toolSet || Object.keys(input.toolSet).length === 0) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return (input.startMcpServer ?? startCodexRuntimeMcpServer)({
|
|
103
|
+
projectDir: input.projectDir,
|
|
104
|
+
toolSet: input.toolSet,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function runtimeToolNames(toolSet) {
|
|
108
|
+
return Object.values(toolSet ?? {}).map((descriptor) => descriptor.name);
|
|
109
|
+
}
|
|
110
|
+
export class CodexKtxLlmRuntime {
|
|
111
|
+
deps;
|
|
112
|
+
runner;
|
|
113
|
+
logger;
|
|
114
|
+
constructor(deps) {
|
|
115
|
+
this.deps = deps;
|
|
116
|
+
this.runner = deps.runner ?? new CodexSdkCliRunner();
|
|
117
|
+
this.logger = deps.logger ?? noopLogger;
|
|
118
|
+
}
|
|
119
|
+
async generateText(input) {
|
|
120
|
+
const startedAt = Date.now();
|
|
121
|
+
const model = modelForRole(this.deps.modelSlots, input.role);
|
|
122
|
+
const mcp = await mcpForTools({
|
|
123
|
+
projectDir: this.deps.projectDir,
|
|
124
|
+
toolSet: input.tools,
|
|
125
|
+
startMcpServer: this.deps.startMcpServer,
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
const config = buildCodexRuntimeConfig({
|
|
129
|
+
model,
|
|
130
|
+
...(mcp
|
|
131
|
+
? {
|
|
132
|
+
mcp: {
|
|
133
|
+
url: mcp.url,
|
|
134
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
135
|
+
bearerToken: mcp.bearerToken,
|
|
136
|
+
toolNames: runtimeToolNames(input.tools),
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
});
|
|
141
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
142
|
+
projectDir: this.deps.projectDir,
|
|
143
|
+
model,
|
|
144
|
+
prompt: promptWithSystem(input.system, input.prompt),
|
|
145
|
+
configOverrides: config.configOverrides,
|
|
146
|
+
env: config.env,
|
|
147
|
+
}));
|
|
148
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
149
|
+
input.onMetrics?.(metrics(summary, startedAt));
|
|
150
|
+
return assertSuccessfulText(summary, collected.streamError);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await mcp?.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async generateObject(input) {
|
|
157
|
+
const startedAt = Date.now();
|
|
158
|
+
const model = modelForRole(this.deps.modelSlots, input.role);
|
|
159
|
+
const mcp = await mcpForTools({
|
|
160
|
+
projectDir: this.deps.projectDir,
|
|
161
|
+
toolSet: input.tools,
|
|
162
|
+
startMcpServer: this.deps.startMcpServer,
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
const config = buildCodexRuntimeConfig({
|
|
166
|
+
model,
|
|
167
|
+
...(mcp
|
|
168
|
+
? {
|
|
169
|
+
mcp: {
|
|
170
|
+
url: mcp.url,
|
|
171
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
172
|
+
bearerToken: mcp.bearerToken,
|
|
173
|
+
toolNames: runtimeToolNames(input.tools),
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
});
|
|
178
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
179
|
+
projectDir: this.deps.projectDir,
|
|
180
|
+
model,
|
|
181
|
+
prompt: promptWithSystem(input.system, input.prompt),
|
|
182
|
+
configOverrides: config.configOverrides,
|
|
183
|
+
env: config.env,
|
|
184
|
+
outputSchema: z.toJSONSchema(input.schema, { target: 'draft-7' }),
|
|
185
|
+
}));
|
|
186
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
187
|
+
input.onMetrics?.(metrics(summary, startedAt));
|
|
188
|
+
return parseStructuredOutput(input.schema, assertSuccessfulText(summary, collected.streamError));
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await mcp?.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async runAgentLoop(params) {
|
|
195
|
+
const startedAt = Date.now();
|
|
196
|
+
const model = modelForRole(this.deps.modelSlots, params.modelRole);
|
|
197
|
+
let mcp;
|
|
198
|
+
try {
|
|
199
|
+
mcp = await mcpForTools({
|
|
200
|
+
projectDir: this.deps.projectDir,
|
|
201
|
+
toolSet: params.toolSet,
|
|
202
|
+
startMcpServer: this.deps.startMcpServer,
|
|
203
|
+
});
|
|
204
|
+
const config = buildCodexRuntimeConfig({
|
|
205
|
+
model,
|
|
206
|
+
...(mcp
|
|
207
|
+
? {
|
|
208
|
+
mcp: {
|
|
209
|
+
url: mcp.url,
|
|
210
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
211
|
+
bearerToken: mcp.bearerToken,
|
|
212
|
+
toolNames: runtimeToolNames(params.toolSet),
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
: {}),
|
|
216
|
+
});
|
|
217
|
+
const abortController = new AbortController();
|
|
218
|
+
const onStep = async (stepIndex) => {
|
|
219
|
+
try {
|
|
220
|
+
await params.onStepFinish?.({ stepIndex, stepBudget: params.stepBudget });
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
this.logger.warn(`[codex-runner] onStepFinish callback threw; ignoring: ${error instanceof Error ? error.message : String(error)}`);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
227
|
+
projectDir: this.deps.projectDir,
|
|
228
|
+
model,
|
|
229
|
+
prompt: promptWithSystem(params.systemPrompt, params.userPrompt),
|
|
230
|
+
configOverrides: config.configOverrides,
|
|
231
|
+
env: config.env,
|
|
232
|
+
signal: abortController.signal,
|
|
233
|
+
}), { stepBudget: params.stepBudget, abortController, onStep });
|
|
234
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
235
|
+
const error = summaryError(summary, collected.streamError);
|
|
236
|
+
const stopReason = collected.budgetExceeded ? 'budget' : error ? 'error' : summary.stopReason;
|
|
237
|
+
return {
|
|
238
|
+
stopReason,
|
|
239
|
+
...(stopReason === 'error' && error ? { error } : {}),
|
|
240
|
+
metrics: {
|
|
241
|
+
totalMs: Date.now() - startedAt,
|
|
242
|
+
usage: summary.usage,
|
|
243
|
+
stepCount: summary.stepCount,
|
|
244
|
+
stepBoundariesMs: summary.stepBoundariesMs,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
250
|
+
return {
|
|
251
|
+
stopReason: 'error',
|
|
252
|
+
error: err,
|
|
253
|
+
metrics: { totalMs: Date.now() - startedAt, usage: {}, stepCount: 0, stepBoundariesMs: [] },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await mcp?.close();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// A rejected model is not an auth failure: Codex authenticated, connected, and
|
|
262
|
+
// the API refused the model id. These markers come from the API error envelope
|
|
263
|
+
// (e.g. "model is not supported", "invalid_request_error").
|
|
264
|
+
const MODEL_UNAVAILABLE_MARKERS = /\bnot supported\b|\bnot available\b|\bdoes not exist\b|invalid_request_error|\bunknown model\b|\bunsupported model\b/i;
|
|
265
|
+
function describeCodexProbeFailure(model, message) {
|
|
266
|
+
if (MODEL_UNAVAILABLE_MARKERS.test(message)) {
|
|
267
|
+
const fix = `Run \`codex\` to see the models your account supports, then set llm.models.default in ktx.yaml (or rerun \`ktx setup\`).`;
|
|
268
|
+
return {
|
|
269
|
+
message: `Codex is authenticated, but the configured model "${model}" is not available for this Codex account. ${fix} Details: ${message}`,
|
|
270
|
+
fix,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const fix = `Authenticate Codex locally with the Codex CLI, verify the Codex CLI is installed, then rerun setup or \`ktx status\`.`;
|
|
274
|
+
return {
|
|
275
|
+
message: `Codex authentication is not usable. ${fix} Details: ${message}`,
|
|
276
|
+
fix,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export async function runCodexAuthProbe(input) {
|
|
280
|
+
let model;
|
|
281
|
+
try {
|
|
282
|
+
model = resolveCodexModel(input.model);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
return {
|
|
286
|
+
ok: false,
|
|
287
|
+
message: error instanceof Error ? error.message : String(error),
|
|
288
|
+
fix: 'Set llm.models.default in ktx.yaml to a supported codex model (codex, default, or a gpt-* / codex-* id), or rerun `ktx setup`.',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const runtime = new CodexKtxLlmRuntime({
|
|
292
|
+
projectDir: input.projectDir,
|
|
293
|
+
modelSlots: { default: model },
|
|
294
|
+
...(input.runner ? { runner: input.runner } : {}),
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
await runtime.generateText({ role: 'default', prompt: 'Reply with exactly: ok' });
|
|
298
|
+
return { ok: true };
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
return { ok: false, ...describeCodexProbeFailure(model, message) };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CodexSdkRunnerInput {
|
|
2
|
+
projectDir: string;
|
|
3
|
+
model: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
configOverrides?: Record<string, unknown>;
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
outputSchema?: Record<string, unknown>;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
export interface CodexSdkRunner {
|
|
11
|
+
runStreamed(input: CodexSdkRunnerInput): Promise<AsyncIterable<unknown>>;
|
|
12
|
+
}
|
|
13
|
+
export interface CodexSdkCliRunnerOptions {
|
|
14
|
+
envBase?: NodeJS.ProcessEnv;
|
|
15
|
+
codexPathOverride?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class CodexSdkCliRunner implements CodexSdkRunner {
|
|
18
|
+
private readonly options;
|
|
19
|
+
constructor(options?: CodexSdkCliRunnerOptions);
|
|
20
|
+
runStreamed(input: CodexSdkRunnerInput): Promise<AsyncIterable<unknown>>;
|
|
21
|
+
}
|
|
@@ -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
|
}
|
|
@@ -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<{
|
|
@@ -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".'),
|
|
@@ -59,6 +59,13 @@ function optionalString(raw, field) {
|
|
|
59
59
|
}
|
|
60
60
|
throw new Error(`sql analysis response has invalid optional string field ${field}`);
|
|
61
61
|
}
|
|
62
|
+
function optionalNullableStringField(raw, field) {
|
|
63
|
+
const value = raw[field];
|
|
64
|
+
if (value === null || value === undefined || typeof value === 'string') {
|
|
65
|
+
return value ?? null;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`sql analysis response has invalid optional nullable string field ${field}`);
|
|
68
|
+
}
|
|
62
69
|
function requiredStringArray(raw, field) {
|
|
63
70
|
const value = raw[field];
|
|
64
71
|
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
@@ -136,10 +143,32 @@ function mapColumnsByClause(raw) {
|
|
|
136
143
|
}
|
|
137
144
|
return result;
|
|
138
145
|
}
|
|
146
|
+
function requiredTableRef(raw, field) {
|
|
147
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
148
|
+
throw new Error(`sql analysis response contains invalid table ref in ${field}`);
|
|
149
|
+
}
|
|
150
|
+
const record = raw;
|
|
151
|
+
const name = record.name;
|
|
152
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
153
|
+
throw new Error(`sql analysis response table ref in ${field} is missing name`);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
catalog: optionalNullableStringField(record, 'catalog'),
|
|
157
|
+
db: optionalNullableStringField(record, 'db'),
|
|
158
|
+
name,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function requiredTableRefArray(raw, field) {
|
|
162
|
+
const value = raw[field];
|
|
163
|
+
if (!Array.isArray(value)) {
|
|
164
|
+
throw new Error(`sql analysis response is missing table-ref[] field ${field}`);
|
|
165
|
+
}
|
|
166
|
+
return value.map((item, index) => requiredTableRef(item, `${field}.${index}`));
|
|
167
|
+
}
|
|
139
168
|
function mapBatchResult(raw) {
|
|
140
169
|
const error = optionalString(raw, 'error');
|
|
141
170
|
return {
|
|
142
|
-
tablesTouched:
|
|
171
|
+
tablesTouched: requiredTableRefArray(raw, 'tables_touched'),
|
|
143
172
|
columnsByClause: mapColumnsByClause(raw),
|
|
144
173
|
...(error !== undefined ? { error } : {}),
|
|
145
174
|
};
|
|
@@ -170,10 +199,11 @@ export function createHttpSqlAnalysisPort(options) {
|
|
|
170
199
|
});
|
|
171
200
|
return mapResult(raw);
|
|
172
201
|
},
|
|
173
|
-
async analyzeBatch(items, dialect) {
|
|
202
|
+
async analyzeBatch(items, dialect, options) {
|
|
174
203
|
const raw = await requestJson('/sql/analyze-batch', {
|
|
175
204
|
dialect,
|
|
176
205
|
items,
|
|
206
|
+
...(options?.catalog ? { catalog: options.catalog } : {}),
|
|
177
207
|
});
|
|
178
208
|
return mapBatchResponse(raw);
|
|
179
209
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KtxTableRef } from '../scan/types.js';
|
|
1
2
|
export type SqlAnalysisDialect = 'bigquery' | 'snowflake' | 'postgres' | 'redshift' | 'mysql' | 'sqlite' | 'tsql' | 'clickhouse' | (string & {});
|
|
2
3
|
export type SqlAnalysisLiteralSlotType = 'string' | 'number' | 'timestamp' | 'date' | 'boolean' | 'null' | 'unknown';
|
|
3
4
|
export interface SqlAnalysisLiteralSlot {
|
|
@@ -17,8 +18,17 @@ export interface SqlAnalysisBatchItem {
|
|
|
17
18
|
id: string;
|
|
18
19
|
sql: string;
|
|
19
20
|
}
|
|
21
|
+
interface SqlAnalysisCatalogTable extends KtxTableRef {
|
|
22
|
+
columns?: string[];
|
|
23
|
+
}
|
|
24
|
+
interface SqlAnalysisCatalog {
|
|
25
|
+
tables: SqlAnalysisCatalogTable[];
|
|
26
|
+
}
|
|
27
|
+
export interface SqlAnalysisBatchOptions {
|
|
28
|
+
catalog?: SqlAnalysisCatalog;
|
|
29
|
+
}
|
|
20
30
|
export interface SqlAnalysisBatchResult {
|
|
21
|
-
tablesTouched:
|
|
31
|
+
tablesTouched: KtxTableRef[];
|
|
22
32
|
columnsByClause: Partial<Record<SqlAnalysisClause, string[]>>;
|
|
23
33
|
error?: string | null;
|
|
24
34
|
}
|
|
@@ -28,7 +38,7 @@ export interface SqlReadOnlyValidationResult {
|
|
|
28
38
|
}
|
|
29
39
|
export interface SqlAnalysisPort {
|
|
30
40
|
analyzeForFingerprint(sql: string, dialect: SqlAnalysisDialect): Promise<SqlAnalysisFingerprintResult>;
|
|
31
|
-
analyzeBatch(items: SqlAnalysisBatchItem[], dialect: SqlAnalysisDialect): Promise<Map<string, SqlAnalysisBatchResult>>;
|
|
41
|
+
analyzeBatch(items: SqlAnalysisBatchItem[], dialect: SqlAnalysisDialect, options?: SqlAnalysisBatchOptions): Promise<Map<string, SqlAnalysisBatchResult>>;
|
|
32
42
|
validateReadOnly(sql: string, dialect: SqlAnalysisDialect): Promise<SqlReadOnlyValidationResult>;
|
|
33
43
|
}
|
|
34
44
|
export {};
|
|
@@ -8,8 +8,8 @@ declare const contextCandidateMarkInputSchema: z.ZodObject<{
|
|
|
8
8
|
conflict: "conflict";
|
|
9
9
|
merged: "merged";
|
|
10
10
|
pending: "pending";
|
|
11
|
-
promoted: "promoted";
|
|
12
11
|
rejected: "rejected";
|
|
12
|
+
promoted: "promoted";
|
|
13
13
|
}>;
|
|
14
14
|
rejectionReason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
15
15
|
}, z.core.$strip>;
|
|
@@ -31,8 +31,8 @@ export declare class ContextCandidateMarkTool extends BaseTool<typeof contextCan
|
|
|
31
31
|
conflict: "conflict";
|
|
32
32
|
merged: "merged";
|
|
33
33
|
pending: "pending";
|
|
34
|
-
promoted: "promoted";
|
|
35
34
|
rejected: "rejected";
|
|
35
|
+
promoted: "promoted";
|
|
36
36
|
}>;
|
|
37
37
|
rejectionReason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
38
38
|
}, z.core.$strip>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { buildPublicIngestPlan, executePublicIngestTarget, publicProgressMessage } from './public-ingest.js';
|
|
2
|
+
import { createAggregateProgressPort } from './progress-port-adapter.js';
|
|
3
3
|
import { formatDuration } from './demo-metrics.js';
|
|
4
4
|
import { profileMark } from './startup-profile.js';
|
|
5
5
|
profileMark('module:context-build-view');
|
|
@@ -618,38 +618,10 @@ export function initViewState(targets) {
|
|
|
618
618
|
totalElapsedMs: 0,
|
|
619
619
|
};
|
|
620
620
|
}
|
|
621
|
-
function publicProgressMessage(message, target) {
|
|
622
|
-
let current = message;
|
|
623
|
-
if (target.operation === 'database-ingest') {
|
|
624
|
-
current = publicDatabaseIngestMessage(current);
|
|
625
|
-
}
|
|
626
|
-
if (target.steps.includes('query-history')) {
|
|
627
|
-
current = publicQueryHistoryMessage(current, target.connectionId);
|
|
628
|
-
}
|
|
629
|
-
return current;
|
|
630
|
-
}
|
|
631
621
|
function formatProgressDetail(update, target) {
|
|
632
622
|
const percent = Math.max(0, Math.min(100, Math.round(update.percent)));
|
|
633
623
|
return `[${percent}%] ${publicProgressMessage(update.message, target)}`;
|
|
634
624
|
}
|
|
635
|
-
function createContextBuildProgressPort(onProgress, state = { progress: 0 }, start = 0, weight = 1) {
|
|
636
|
-
return {
|
|
637
|
-
async update(value, message, options) {
|
|
638
|
-
const absoluteValue = start + Math.max(0, Math.min(1, value)) * weight;
|
|
639
|
-
state.progress = Math.max(state.progress, Math.min(1, absoluteValue));
|
|
640
|
-
if (!message)
|
|
641
|
-
return;
|
|
642
|
-
onProgress({
|
|
643
|
-
percent: Math.max(0, Math.min(100, Math.round(state.progress * 100))),
|
|
644
|
-
message,
|
|
645
|
-
...(options?.transient !== undefined ? { transient: options.transient } : {}),
|
|
646
|
-
});
|
|
647
|
-
},
|
|
648
|
-
startPhase(phaseWeight) {
|
|
649
|
-
return createContextBuildProgressPort(onProgress, state, state.progress, weight * phaseWeight);
|
|
650
|
-
},
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
625
|
export async function runContextBuild(project, args, io, deps = {}) {
|
|
654
626
|
const plan = buildPublicIngestPlan(project, {
|
|
655
627
|
projectDir: args.projectDir,
|
|
@@ -802,7 +774,7 @@ export async function runContextBuild(project, args, io, deps = {}) {
|
|
|
802
774
|
hasPendingProgressPublish = !publishSourceProgress(false);
|
|
803
775
|
};
|
|
804
776
|
const progressDeps = {
|
|
805
|
-
scanProgress:
|
|
777
|
+
scanProgress: createAggregateProgressPort(updateSchemaPhase),
|
|
806
778
|
ingestProgress: updateIngestPhase,
|
|
807
779
|
runtimeIo: io,
|
|
808
780
|
onPhaseStart,
|
|
@@ -811,7 +783,7 @@ export async function runContextBuild(project, args, io, deps = {}) {
|
|
|
811
783
|
let result = null;
|
|
812
784
|
let thrownError = null;
|
|
813
785
|
try {
|
|
814
|
-
result = await execTarget(targetState.target, runArgs, capture.io, progressDeps);
|
|
786
|
+
result = await execTarget(targetState.target, runArgs, capture.io, progressDeps, project);
|
|
815
787
|
}
|
|
816
788
|
catch (error) {
|
|
817
789
|
thrownError = error;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KtxCliIo } from '../cli-runtime.js';
|
|
2
|
+
export interface BufferedCommandIo extends KtxCliIo {
|
|
3
|
+
stdoutText(): string;
|
|
4
|
+
stderrText(): string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Captures stdout/stderr from a command (e.g. `runKtxConnection`) into buffers
|
|
8
|
+
* instead of the terminal. Callers decide whether to flush the captured text to
|
|
9
|
+
* the user or discard it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createBufferedCommandIo(): BufferedCommandIo;
|