@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.
Files changed (83) hide show
  1. package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.9.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-runtime.js +50 -3
  5. package/dist/commands/setup-commands.js +1 -1
  6. package/dist/connection-recovery.d.ts +34 -0
  7. package/dist/connection-recovery.js +82 -0
  8. package/dist/connection.js +3 -1
  9. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  10. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  11. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  12. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  13. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  14. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +29 -0
  15. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +190 -0
  16. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  17. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  18. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  19. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  20. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  21. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  22. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  23. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  24. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  25. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  26. package/dist/context/ingest/local-adapters.js +21 -4
  27. package/dist/context/ingest/local-bundle-runtime.js +3 -2
  28. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  29. package/dist/context/llm/codex-exec-events.js +155 -0
  30. package/dist/context/llm/codex-isolation.d.ts +3 -0
  31. package/dist/context/llm/codex-isolation.js +5 -0
  32. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  33. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  34. package/dist/context/llm/codex-models.d.ts +2 -0
  35. package/dist/context/llm/codex-models.js +17 -0
  36. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  37. package/dist/context/llm/codex-runtime-config.js +19 -0
  38. package/dist/context/llm/codex-runtime.d.ts +37 -0
  39. package/dist/context/llm/codex-runtime.js +304 -0
  40. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  41. package/dist/context/llm/codex-sdk-runner.js +63 -0
  42. package/dist/context/llm/local-config.d.ts +2 -0
  43. package/dist/context/llm/local-config.js +12 -1
  44. package/dist/context/project/config.d.ts +2 -0
  45. package/dist/context/project/config.js +2 -2
  46. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  47. package/dist/context/sql-analysis/ports.d.ts +12 -2
  48. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  49. package/dist/context-build-view.js +4 -32
  50. package/dist/io/buffered-command-io.d.ts +11 -0
  51. package/dist/io/buffered-command-io.js +28 -0
  52. package/dist/llm/types.d.ts +1 -1
  53. package/dist/local-adapters.d.ts +10 -2
  54. package/dist/local-adapters.js +19 -3
  55. package/dist/next-steps.js +1 -2
  56. package/dist/progress-port-adapter.d.ts +6 -0
  57. package/dist/progress-port-adapter.js +18 -0
  58. package/dist/public-ingest.d.ts +20 -1
  59. package/dist/public-ingest.js +178 -27
  60. package/dist/scan.js +3 -1
  61. package/dist/setup-context.d.ts +2 -0
  62. package/dist/setup-context.js +133 -27
  63. package/dist/setup-databases.d.ts +17 -1
  64. package/dist/setup-databases.js +358 -249
  65. package/dist/setup-models.d.ts +10 -1
  66. package/dist/setup-models.js +90 -2
  67. package/dist/setup-ready-menu.d.ts +16 -2
  68. package/dist/setup-ready-menu.js +37 -5
  69. package/dist/setup-sources.js +108 -28
  70. package/dist/setup.js +22 -10
  71. package/dist/status-project.d.ts +11 -0
  72. package/dist/status-project.js +50 -1
  73. package/dist/telemetry/command-hook.d.ts +1 -0
  74. package/dist/telemetry/command-hook.js +3 -1
  75. package/dist/telemetry/events.d.ts +11 -6
  76. package/dist/telemetry/events.js +10 -2
  77. package/dist/telemetry/identity.d.ts +0 -1
  78. package/dist/telemetry/identity.js +6 -6
  79. package/dist/telemetry/index.d.ts +12 -0
  80. package/dist/telemetry/index.js +13 -2
  81. package/dist/telemetry/scrubber.d.ts +10 -0
  82. package/dist/telemetry/scrubber.js +20 -0
  83. package/package.json +5 -4
@@ -47,7 +47,7 @@ export declare class SqliteContextEvidenceStore implements ContextEvidenceIndexS
47
47
  assertion: string;
48
48
  rationale: string;
49
49
  actionHint: string;
50
- status: "conflict" | "merged" | "pending" | "promoted" | "rejected";
50
+ status: "conflict" | "merged" | "pending" | "rejected" | "promoted";
51
51
  promotionScore: number;
52
52
  suggestedPageKey: string | null;
53
53
  evidenceRefs: JsonValue;
@@ -85,18 +85,88 @@ export async function integrateWorkUnitPatch(input) {
85
85
  });
86
86
  }
87
87
  catch (semanticError) {
88
- if (preApplyHead) {
89
- await input.integrationGit.resetHardTo(preApplyHead);
90
- }
88
+ const reason = errorMessage(semanticError);
91
89
  await input.trace.event('error', 'integration', 'patch_semantic_conflict_after_textual_resolution', {
92
90
  unitKey: input.unitKey,
93
91
  patchPath: input.patchPath,
94
92
  touchedPaths: textualResolution.changedPaths,
95
- reason: errorMessage(semanticError),
93
+ reason,
96
94
  });
95
+ // A textual conflict and a semantic-gate failure can co-occur: the resolver
96
+ // reconciles the text but can leave wiki sl_refs pointing at measures the
97
+ // merged source no longer defines. Recover via the same gate repair the
98
+ // clean-apply branch uses, instead of hard-failing the whole job.
99
+ if (input.repairGateFailure) {
100
+ const gateRepair = await input.repairGateFailure({
101
+ unitKey: input.unitKey,
102
+ patchPath: input.patchPath,
103
+ touchedPaths: textualResolution.changedPaths,
104
+ reason,
105
+ });
106
+ if (gateRepair.status !== 'failed') {
107
+ // The resolver wrote its merge to the worktree (unstaged); the repair
108
+ // edited a subset on top. Commit the union so neither is dropped.
109
+ const resolvedAndRepairedPaths = [
110
+ ...new Set([...textualResolution.changedPaths, ...gateRepair.changedPaths]),
111
+ ].sort();
112
+ try {
113
+ await traceTimed(input.trace, 'integration', 'semantic_gate_after_gate_repair', { unitKey: input.unitKey, touchedPaths: gateRepair.changedPaths }, async () => {
114
+ await input.validateAppliedTree(gateRepair.changedPaths);
115
+ });
116
+ const commit = await input.integrationGit.commitFiles(resolvedAndRepairedPaths, `ingest: resolve WorkUnit ${input.unitKey} conflict`, input.author.name, input.author.email);
117
+ if (commit.created) {
118
+ await input.trace.event('debug', 'integration', 'patch_accepted_after_textual_resolution', {
119
+ unitKey: input.unitKey,
120
+ commitSha: commit.commitHash,
121
+ touchedPaths: resolvedAndRepairedPaths,
122
+ attempts: textualResolution.attempts,
123
+ gateRepairAttempts: gateRepair.attempts,
124
+ });
125
+ return {
126
+ status: 'accepted',
127
+ commitSha: commit.commitHash,
128
+ touchedPaths: resolvedAndRepairedPaths,
129
+ textualResolution,
130
+ gateRepair,
131
+ };
132
+ }
133
+ }
134
+ catch (repairValidationError) {
135
+ if (preApplyHead) {
136
+ await input.integrationGit.resetHardTo(preApplyHead);
137
+ }
138
+ await input.trace.event('error', 'integration', 'patch_semantic_conflict_after_textual_resolution', {
139
+ unitKey: input.unitKey,
140
+ patchPath: input.patchPath,
141
+ touchedPaths: gateRepair.changedPaths,
142
+ reason: errorMessage(repairValidationError),
143
+ });
144
+ return {
145
+ status: 'semantic_conflict',
146
+ reason: errorMessage(repairValidationError),
147
+ touchedPaths: gateRepair.changedPaths,
148
+ textualResolution,
149
+ gateRepair,
150
+ };
151
+ }
152
+ }
153
+ if (preApplyHead) {
154
+ await input.integrationGit.resetHardTo(preApplyHead);
155
+ }
156
+ return {
157
+ status: 'semantic_conflict',
158
+ reason: gateRepair.status === 'failed' ? gateRepair.reason : reason,
159
+ touchedPaths: textualResolution.changedPaths,
160
+ textualResolution,
161
+ gateRepair,
162
+ };
163
+ }
164
+ if (preApplyHead) {
165
+ await input.integrationGit.resetHardTo(preApplyHead);
166
+ }
97
167
  return {
98
168
  status: 'semantic_conflict',
99
- reason: errorMessage(semanticError),
169
+ reason,
100
170
  touchedPaths: textualResolution.changedPaths,
101
171
  textualResolution,
102
172
  };
@@ -7,6 +7,7 @@ import { DbtSourceAdapter } from './adapters/dbt/dbt.adapter.js';
7
7
  import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js';
8
8
  import { HistoricSqlSourceAdapter } from './adapters/historic-sql/historic-sql.adapter.js';
9
9
  import { PostgresPgssReader } from './adapters/historic-sql/postgres-pgss-reader.js';
10
+ import { resolveQueryHistoryScopeFloor } from './adapters/historic-sql/scope-floor.js';
10
11
  import { HISTORIC_SQL_SOURCE_KEY, historicSqlUnifiedPullConfigSchema, } from './adapters/historic-sql/types.js';
11
12
  import { createDaemonLiveDatabaseIntrospection, } from './adapters/live-database/daemon-introspection.js';
12
13
  import { LiveDatabaseSourceAdapter } from './adapters/live-database/live-database.adapter.js';
@@ -113,14 +114,30 @@ function queryHistoryRecord(connection) {
113
114
  const queryHistory = isRecord(context?.queryHistory) ? context.queryHistory : null;
114
115
  return queryHistory;
115
116
  }
116
- function queryHistoryPullConfig(connection) {
117
+ async function queryHistoryPullConfig(project, connectionId, connection) {
117
118
  const queryHistory = queryHistoryRecord(connection);
118
119
  if (queryHistory?.enabled !== true || !isRecord(connection))
119
120
  return null;
120
- const dialect = historicSqlDialectByDriver.get(String(connection.driver ?? '').toLowerCase());
121
+ const driver = String(connection.driver ?? '').toLowerCase();
122
+ const dialect = historicSqlDialectByDriver.get(driver);
121
123
  if (!dialect)
122
124
  return null;
123
- return { ...queryHistory, dialect };
125
+ const scopeFloor = await resolveQueryHistoryScopeFloor({
126
+ projectDir: project.projectDir,
127
+ connectionId,
128
+ driver,
129
+ connection,
130
+ storedQueryHistory: queryHistory,
131
+ });
132
+ const { enabled: _enabled, dialect: _dialect, enabledTables: _enabledTables, enabledSchemas: _enabledSchemas, scopeFloorWarnings: _scopeFloorWarnings, ...stored } = queryHistory;
133
+ return {
134
+ ...stored,
135
+ dialect,
136
+ ...(scopeFloor.enabledTables.length > 0 ? { enabledTables: scopeFloor.enabledTables } : {}),
137
+ ...(scopeFloor.enabledSchemas.length > 0 ? { enabledSchemas: scopeFloor.enabledSchemas } : {}),
138
+ ...(scopeFloor.modeledTableCatalog.length > 0 ? { modeledTableCatalog: scopeFloor.modeledTableCatalog } : {}),
139
+ ...(scopeFloor.warnings.length > 0 ? { scopeFloorWarnings: scopeFloor.warnings } : {}),
140
+ };
124
141
  }
125
142
  function stringField(value) {
126
143
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
@@ -168,7 +185,7 @@ export async function localPullConfigForAdapter(project, adapter, connectionId,
168
185
  if (options.historicSqlPullConfigOverride) {
169
186
  return historicSqlUnifiedPullConfigSchema.parse(options.historicSqlPullConfigOverride);
170
187
  }
171
- const queryHistory = queryHistoryPullConfig(connection);
188
+ const queryHistory = await queryHistoryPullConfig(project, connectionId, connection);
172
189
  if (!queryHistory) {
173
190
  throw new Error(`Connection "${connectionId}" does not have context.queryHistory.enabled: true`);
174
191
  }
@@ -488,9 +488,10 @@ function nextLocalJobId() {
488
488
  }
489
489
  function localIngestLlmProviderGuardMessage(projectDir) {
490
490
  return [
491
- 'ktx ingest requires llm.provider.backend: anthropic, vertex, gateway, or claude-code, or an injected agentRunner.',
492
- 'Configure a local Claude Code session or API-backed LLM, then rerun ingest:',
491
+ 'ktx ingest requires llm.provider.backend: anthropic, vertex, gateway, claude-code, or codex, or an injected agentRunner.',
492
+ 'Configure a local Claude Code/Codex session or API-backed LLM, then rerun ingest:',
493
493
  ` ktx setup --project-dir ${projectDir} --llm-backend claude-code --no-input`,
494
+ ` ktx setup --project-dir ${projectDir} --llm-backend codex --llm-model gpt-5.5 --no-input`,
494
495
  ` ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --llm-model claude-sonnet-4-6 --no-input`,
495
496
  ].join('\n');
496
497
  }
@@ -0,0 +1,20 @@
1
+ import type { LlmTokenUsage, RunLoopStopReason } from './runtime-port.js';
2
+ export interface CodexExecEventSummary {
3
+ finalText: string;
4
+ stopReason: RunLoopStopReason;
5
+ usage: LlmTokenUsage;
6
+ stepCount: number;
7
+ stepBoundariesMs: number[];
8
+ toolCallCount: number;
9
+ toolFailures: string[];
10
+ error?: Error;
11
+ }
12
+ interface CodexEventParseOptions {
13
+ startedAt?: number;
14
+ now?: () => number;
15
+ }
16
+ export declare function isCompletedAgentStep(event: unknown): boolean;
17
+ /** @internal */
18
+ export declare function parseCodexExecEventLine(line: string): unknown;
19
+ export declare function summarizeCodexExecEvents(events: Iterable<unknown>, options?: CodexEventParseOptions): CodexExecEventSummary;
20
+ export {};
@@ -0,0 +1,155 @@
1
+ function record(value) {
2
+ return value && typeof value === 'object' ? value : undefined;
3
+ }
4
+ /**
5
+ * Codex thread items that represent a discrete agent action consuming one loop
6
+ * step. The step budget caps the total number of these regardless of which
7
+ * capability the agent reaches for, so built-in `command_execution` (and any
8
+ * file/web action the public Codex surface still exposes) count alongside our
9
+ * own `mcp_tool_call` items rather than only the MCP ones.
10
+ */
11
+ const AGENT_STEP_ITEM_TYPES = new Set(['command_execution', 'mcp_tool_call', 'file_change', 'web_search']);
12
+ export function isCompletedAgentStep(event) {
13
+ const eventRecord = record(event);
14
+ if (eventRecord?.type !== 'item.completed') {
15
+ return false;
16
+ }
17
+ const itemType = record(eventRecord.item)?.type;
18
+ return typeof itemType === 'string' && AGENT_STEP_ITEM_TYPES.has(itemType);
19
+ }
20
+ function text(value) {
21
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
22
+ }
23
+ function numberValue(value) {
24
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
25
+ }
26
+ function usageFrom(value) {
27
+ const usage = record(value);
28
+ if (!usage) {
29
+ return {};
30
+ }
31
+ const inputTokens = numberValue(usage.input_tokens ?? usage.inputTokens);
32
+ const outputTokens = numberValue(usage.output_tokens ?? usage.outputTokens);
33
+ const explicitTotalTokens = numberValue(usage.total_tokens ?? usage.totalTokens);
34
+ const totalTokens = explicitTotalTokens ??
35
+ (inputTokens !== undefined && outputTokens !== undefined ? inputTokens + outputTokens : undefined);
36
+ return {
37
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
38
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
39
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
40
+ };
41
+ }
42
+ function stopReasonFrom(value) {
43
+ const reason = text(value)?.toLowerCase();
44
+ if (reason && /(budget|max_turn|max-turn|limit)/.test(reason)) {
45
+ return 'budget';
46
+ }
47
+ return 'natural';
48
+ }
49
+ function errorMessageFrom(value) {
50
+ if (value instanceof Error) {
51
+ return value.message;
52
+ }
53
+ const asRecord = record(value);
54
+ const message = text(asRecord?.message);
55
+ return message ?? text(value) ?? 'Codex turn failed';
56
+ }
57
+ /**
58
+ * Codex serializes API failures as a JSON envelope inside the event message
59
+ * (e.g. `{"type":"error","status":400,"error":{"message":"…"}}`). Surface the
60
+ * human-readable inner message so callers don't leak raw JSON; pass plain
61
+ * strings through unchanged.
62
+ */
63
+ function unwrapCodexApiErrorMessage(raw) {
64
+ const trimmed = raw.trim();
65
+ if (!trimmed.startsWith('{')) {
66
+ return raw;
67
+ }
68
+ try {
69
+ const parsed = record(JSON.parse(trimmed));
70
+ return text(record(parsed?.error)?.message) ?? text(parsed?.message) ?? raw;
71
+ }
72
+ catch {
73
+ return raw;
74
+ }
75
+ }
76
+ /** @internal */
77
+ export function parseCodexExecEventLine(line) {
78
+ try {
79
+ return JSON.parse(line);
80
+ }
81
+ catch (error) {
82
+ throw new Error(`Codex JSONL event stream was malformed: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ }
85
+ export function summarizeCodexExecEvents(events, options = {}) {
86
+ const startedAt = options.startedAt ?? Date.now();
87
+ const now = options.now ?? Date.now;
88
+ let finalText = '';
89
+ let stopReason = 'natural';
90
+ let usage = {};
91
+ let turnCount = 0;
92
+ let completedStepCount = 0;
93
+ const stepBoundariesMs = [];
94
+ let toolCallCount = 0;
95
+ const toolFailures = [];
96
+ let error;
97
+ for (const event of events) {
98
+ const eventRecord = record(event);
99
+ const eventType = text(eventRecord?.type);
100
+ if (!eventRecord || !eventType) {
101
+ continue;
102
+ }
103
+ if (eventType === 'turn.started') {
104
+ turnCount += 1;
105
+ continue;
106
+ }
107
+ const item = record(eventRecord.item);
108
+ const itemType = text(item?.type);
109
+ if (eventType === 'item.started' && itemType === 'mcp_tool_call') {
110
+ toolCallCount += 1;
111
+ continue;
112
+ }
113
+ if (isCompletedAgentStep(event)) {
114
+ completedStepCount += 1;
115
+ stepBoundariesMs.push(now() - startedAt);
116
+ // Only MCP tool calls fail the loop: a non-zero `command_execution` exit
117
+ // is normal agent exploration, not a runtime error. `status` is the
118
+ // authoritative signal (the SDK always sets it); the SDK also serializes
119
+ // `error: null` on successful calls, so an explicit-null `error` must NOT
120
+ // be read as a failure — only a populated error object counts.
121
+ if (itemType === 'mcp_tool_call' && (item?.status === 'failed' || (item?.error !== undefined && item?.error !== null))) {
122
+ const name = text(item?.name) ?? text(item?.tool) ?? text(item?.tool_name) ?? 'unknown';
123
+ toolFailures.push(`${name}: ${errorMessageFrom(item?.error)}`);
124
+ }
125
+ continue;
126
+ }
127
+ if (eventType === 'item.completed' && itemType === 'agent_message') {
128
+ finalText = text(item?.text) ?? finalText;
129
+ continue;
130
+ }
131
+ if (eventType === 'turn.completed') {
132
+ usage = usageFrom(eventRecord.usage);
133
+ if (completedStepCount === 0) {
134
+ stepBoundariesMs.push(now() - startedAt);
135
+ }
136
+ stopReason = stopReasonFrom(eventRecord.reason ?? eventRecord.stop_reason ?? eventRecord.terminal_reason);
137
+ continue;
138
+ }
139
+ if (eventType === 'turn.failed' || eventType === 'error') {
140
+ stopReason = 'error';
141
+ error = new Error(unwrapCodexApiErrorMessage(errorMessageFrom(eventRecord.error ?? eventRecord.message)));
142
+ continue;
143
+ }
144
+ }
145
+ return {
146
+ finalText,
147
+ stopReason,
148
+ usage,
149
+ stepCount: completedStepCount > 0 ? completedStepCount : turnCount,
150
+ stepBoundariesMs,
151
+ toolCallCount,
152
+ toolFailures,
153
+ ...(error ? { error } : {}),
154
+ };
155
+ }
@@ -0,0 +1,3 @@
1
+ export declare const CODEX_ISOLATION_WARNING = "Codex backend isolation is limited by the public Codex SDK/CLI surface: ktx restricts the runtime MCP server to the current ktx tool set, disables Codex web search, asks for a read-only sandbox, and sets approval_policy=never, but Codex may still load user Codex config and built-in command execution or read-only file capabilities.";
2
+ export declare const CODEX_ISOLATION_WARNING_FIX = "Use llm.provider.backend: claude-code when you need stricter Claude-Code-style runtime tool isolation, or remove host Codex MCP/tool config before running untrusted prompts through the codex backend.";
3
+ export declare function formatCodexIsolationWarning(): string;
@@ -0,0 +1,5 @@
1
+ export const CODEX_ISOLATION_WARNING = 'Codex backend isolation is limited by the public Codex SDK/CLI surface: ktx restricts the runtime MCP server to the current ktx tool set, disables Codex web search, asks for a read-only sandbox, and sets approval_policy=never, but Codex may still load user Codex config and built-in command execution or read-only file capabilities.';
2
+ export const CODEX_ISOLATION_WARNING_FIX = 'Use llm.provider.backend: claude-code when you need stricter Claude-Code-style runtime tool isolation, or remove host Codex MCP/tool config before running untrusted prompts through the codex backend.';
3
+ export function formatCodexIsolationWarning() {
4
+ return `${CODEX_ISOLATION_WARNING} ${CODEX_ISOLATION_WARNING_FIX}`;
5
+ }
@@ -0,0 +1,24 @@
1
+ import type { KtxMcpServerLike } from '../mcp/types.js';
2
+ import { runKtxMcpHttpServer } from '../../mcp-http-server.js';
3
+ import type { KtxRuntimeToolSet } from './runtime-port.js';
4
+ /** @internal */
5
+ export interface CreateCodexRuntimeMcpServerInput {
6
+ server?: KtxMcpServerLike;
7
+ toolSet: KtxRuntimeToolSet;
8
+ }
9
+ export interface CodexRuntimeMcpServerHandle {
10
+ url: string;
11
+ bearerTokenEnvVar: 'KTX_CODEX_RUNTIME_MCP_TOKEN';
12
+ bearerToken: string;
13
+ close(): Promise<void>;
14
+ }
15
+ type RunServer = typeof runKtxMcpHttpServer;
16
+ export interface StartCodexRuntimeMcpServerInput {
17
+ projectDir: string;
18
+ toolSet: KtxRuntimeToolSet;
19
+ runServer?: RunServer;
20
+ }
21
+ /** @internal */
22
+ export declare function createCodexRuntimeMcpServer(input: CreateCodexRuntimeMcpServerInput): KtxMcpServerLike;
23
+ export declare function startCodexRuntimeMcpServer(input: StartCodexRuntimeMcpServerInput): Promise<CodexRuntimeMcpServerHandle>;
24
+ export {};
@@ -0,0 +1,51 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { runKtxMcpHttpServer } from '../../mcp-http-server.js';
4
+ import { normalizeKtxRuntimeToolOutput } from './runtime-tools.js';
5
+ /** @internal */
6
+ export function createCodexRuntimeMcpServer(input) {
7
+ const server = input.server ??
8
+ new McpServer({
9
+ name: 'ktx-runtime',
10
+ version: '0.0.0',
11
+ });
12
+ for (const descriptor of Object.values(input.toolSet)) {
13
+ server.registerTool(descriptor.name, {
14
+ description: descriptor.description,
15
+ inputSchema: descriptor.inputSchema.shape,
16
+ }, async (toolInput) => {
17
+ const normalized = normalizeKtxRuntimeToolOutput(await descriptor.execute(toolInput));
18
+ return {
19
+ content: [{ type: 'text', text: normalized.markdown }],
20
+ ...(normalized.structured !== undefined && normalized.structured !== null && typeof normalized.structured === 'object'
21
+ ? { structuredContent: normalized.structured }
22
+ : {}),
23
+ };
24
+ });
25
+ }
26
+ return server;
27
+ }
28
+ function serverPort(server, fallback) {
29
+ const address = server.address();
30
+ return typeof address === 'object' && address ? address.port : fallback;
31
+ }
32
+ export async function startCodexRuntimeMcpServer(input) {
33
+ const bearerToken = randomBytes(32).toString('hex');
34
+ const runServer = input.runServer ?? runKtxMcpHttpServer;
35
+ const handle = (await runServer({
36
+ projectDir: input.projectDir,
37
+ host: '127.0.0.1',
38
+ port: 0,
39
+ token: bearerToken,
40
+ allowedHosts: ['127.0.0.1', 'localhost'],
41
+ allowedOrigins: [],
42
+ createMcpServer: () => createCodexRuntimeMcpServer({ toolSet: input.toolSet }),
43
+ }));
44
+ const port = serverPort(handle.server, 0);
45
+ return {
46
+ url: `http://127.0.0.1:${port}/mcp`,
47
+ bearerTokenEnvVar: 'KTX_CODEX_RUNTIME_MCP_TOKEN',
48
+ bearerToken,
49
+ close: () => handle.close(),
50
+ };
51
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_CODEX_MODEL = "gpt-5.5";
2
+ export declare function resolveCodexModel(model: string): string;
@@ -0,0 +1,17 @@
1
+ export const DEFAULT_CODEX_MODEL = 'gpt-5.5';
2
+ const CODEX_MODEL_ALIASES = {
3
+ codex: DEFAULT_CODEX_MODEL,
4
+ default: DEFAULT_CODEX_MODEL,
5
+ };
6
+ const EXPLICIT_CODEX_MODEL_ID = /^(?:gpt|codex)-[a-z0-9][a-z0-9._-]*$/i;
7
+ export function resolveCodexModel(model) {
8
+ const normalized = model.trim();
9
+ const alias = CODEX_MODEL_ALIASES[normalized];
10
+ if (alias) {
11
+ return alias;
12
+ }
13
+ if (EXPLICIT_CODEX_MODEL_ID.test(normalized)) {
14
+ return normalized;
15
+ }
16
+ throw new Error(`Unsupported Codex model "${model}". Use codex, default, or a gpt-* / codex-* model id.`);
17
+ }
@@ -0,0 +1,16 @@
1
+ interface CodexRuntimeMcpConfig {
2
+ url: string;
3
+ bearerTokenEnvVar: string;
4
+ bearerToken: string;
5
+ toolNames: string[];
6
+ }
7
+ export interface BuildCodexRuntimeConfigInput {
8
+ model: string;
9
+ mcp?: CodexRuntimeMcpConfig;
10
+ }
11
+ export interface CodexRuntimeConfig {
12
+ configOverrides: Record<string, unknown>;
13
+ env: Record<string, string>;
14
+ }
15
+ export declare function buildCodexRuntimeConfig(input: BuildCodexRuntimeConfigInput): CodexRuntimeConfig;
16
+ export {};
@@ -0,0 +1,19 @@
1
+ export function buildCodexRuntimeConfig(input) {
2
+ const configOverrides = {
3
+ history: { persistence: 'none' },
4
+ };
5
+ const env = {};
6
+ if (input.mcp) {
7
+ configOverrides.mcp_servers = {
8
+ ktx: {
9
+ url: input.mcp.url,
10
+ bearer_token_env_var: input.mcp.bearerTokenEnvVar,
11
+ enabled_tools: input.mcp.toolNames,
12
+ default_tools_approval_mode: 'approve',
13
+ required: true,
14
+ },
15
+ };
16
+ env[input.mcp.bearerTokenEnvVar] = input.mcp.bearerToken;
17
+ }
18
+ return { configOverrides, env };
19
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { type KtxLogger } from '../core/config.js';
3
+ import { type CodexRuntimeMcpServerHandle } from './codex-mcp-runtime-server.js';
4
+ import { type CodexSdkRunner } from './codex-sdk-runner.js';
5
+ import type { KtxGenerateObjectInput, KtxGenerateTextInput, KtxLlmRuntimePort, KtxRuntimeToolSet, RunLoopParams, RunLoopResult } from './runtime-port.js';
6
+ export interface CodexKtxLlmRuntimeDeps {
7
+ projectDir: string;
8
+ modelSlots: {
9
+ default: string;
10
+ } & Partial<Record<string, string>>;
11
+ runner?: CodexSdkRunner;
12
+ startMcpServer?: (input: {
13
+ projectDir: string;
14
+ toolSet: KtxRuntimeToolSet;
15
+ }) => Promise<CodexRuntimeMcpServerHandle>;
16
+ logger?: KtxLogger;
17
+ }
18
+ export declare class CodexKtxLlmRuntime implements KtxLlmRuntimePort {
19
+ private readonly deps;
20
+ private readonly runner;
21
+ private readonly logger;
22
+ constructor(deps: CodexKtxLlmRuntimeDeps);
23
+ generateText(input: KtxGenerateTextInput): Promise<string>;
24
+ generateObject<TOutput, TSchema extends z.ZodType<TOutput>>(input: KtxGenerateObjectInput<TOutput, TSchema>): Promise<TOutput>;
25
+ runAgentLoop(params: RunLoopParams): Promise<RunLoopResult>;
26
+ }
27
+ export declare function runCodexAuthProbe(input: {
28
+ projectDir: string;
29
+ model: string;
30
+ runner?: CodexSdkRunner;
31
+ }): Promise<{
32
+ ok: true;
33
+ } | {
34
+ ok: false;
35
+ message: string;
36
+ fix: string;
37
+ }>;