@kaelio/ktx 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.8.0-py3-none-any.whl → kaelio_ktx-0.10.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/clack.d.ts +6 -0
- package/dist/clack.js +17 -2
- package/dist/cli-program.d.ts +3 -0
- package/dist/cli-program.js +42 -2
- package/dist/cli-runtime.d.ts +3 -0
- package/dist/cli-runtime.js +94 -3
- package/dist/commands/setup-commands.js +3 -4
- package/dist/connection-recovery.d.ts +34 -0
- package/dist/connection-recovery.js +82 -0
- package/dist/connection.js +26 -2
- package/dist/connectors/bigquery/connector.d.ts +2 -5
- package/dist/connectors/bigquery/connector.js +2 -2
- package/dist/connectors/clickhouse/connector.d.ts +2 -5
- package/dist/connectors/clickhouse/connector.js +2 -2
- package/dist/connectors/mysql/connector.d.ts +7 -6
- package/dist/connectors/mysql/connector.js +25 -5
- package/dist/connectors/mysql/dialect.d.ts +1 -1
- package/dist/connectors/mysql/dialect.js +12 -2
- package/dist/connectors/postgres/connector.d.ts +2 -5
- package/dist/connectors/postgres/connector.js +2 -2
- package/dist/connectors/snowflake/connector.d.ts +2 -5
- package/dist/connectors/snowflake/connector.js +2 -2
- package/dist/connectors/sqlite/connector.d.ts +2 -5
- package/dist/connectors/sqlite/connector.js +2 -2
- package/dist/connectors/sqlserver/connector.d.ts +2 -5
- package/dist/connectors/sqlserver/connector.js +2 -2
- package/dist/context/connections/drivers.d.ts +0 -1
- package/dist/context/connections/drivers.js +0 -7
- package/dist/context/connections/query-executor.d.ts +2 -1
- package/dist/context/core/abort.d.ts +9 -0
- package/dist/context/core/abort.js +36 -0
- package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
- package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
- package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +30 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +194 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
- package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
- package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
- package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
- package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
- package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
- package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
- package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
- package/dist/context/ingest/final-gate-repair.d.ts +1 -0
- package/dist/context/ingest/final-gate-repair.js +1 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
- package/dist/context/ingest/ingest-bundle.runner.js +127 -53
- package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +13 -5
- package/dist/context/ingest/local-ingest.d.ts +1 -0
- package/dist/context/ingest/local-ingest.js +13 -3
- package/dist/context/ingest/memory-flow/events.js +1 -1
- package/dist/context/ingest/memory-flow/schema.js +8 -3
- package/dist/context/ingest/memory-flow/types.d.ts +7 -3
- package/dist/context/ingest/ports.d.ts +3 -5
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
- package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/types.d.ts +1 -0
- package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
- package/dist/context/llm/ai-sdk-runtime.js +152 -16
- package/dist/context/llm/claude-code-runtime.d.ts +6 -4
- package/dist/context/llm/claude-code-runtime.js +127 -48
- package/dist/context/llm/codex-exec-events.d.ts +20 -0
- package/dist/context/llm/codex-exec-events.js +155 -0
- package/dist/context/llm/codex-isolation.d.ts +3 -0
- package/dist/context/llm/codex-isolation.js +5 -0
- package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
- package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
- package/dist/context/llm/codex-models.d.ts +2 -0
- package/dist/context/llm/codex-models.js +17 -0
- package/dist/context/llm/codex-runtime-config.d.ts +16 -0
- package/dist/context/llm/codex-runtime-config.js +19 -0
- package/dist/context/llm/codex-runtime.d.ts +37 -0
- package/dist/context/llm/codex-runtime.js +347 -0
- package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
- package/dist/context/llm/codex-sdk-runner.js +63 -0
- package/dist/context/llm/local-config.d.ts +16 -4
- package/dist/context/llm/local-config.js +18 -2
- package/dist/context/llm/rate-limit-governor.d.ts +103 -0
- package/dist/context/llm/rate-limit-governor.js +285 -0
- package/dist/context/llm/runtime-port.d.ts +3 -6
- package/dist/context/mcp/context-tools.js +43 -13
- package/dist/context/project/config.d.ts +14 -0
- package/dist/context/project/config.js +37 -2
- package/dist/context/scan/types.d.ts +15 -2
- package/dist/context/scan/types.js +12 -0
- package/dist/context/sl/description-normalization.js +4 -14
- package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
- package/dist/context/sql-analysis/ports.d.ts +12 -2
- package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
- package/dist/context-build-view.d.ts +13 -0
- package/dist/context-build-view.js +63 -32
- package/dist/demo-metrics.d.ts +0 -2
- package/dist/demo-metrics.js +1 -11
- package/dist/ingest.d.ts +1 -0
- package/dist/ingest.js +32 -3
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -0
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/llm/types.d.ts +1 -1
- package/dist/local-adapters.d.ts +10 -2
- package/dist/local-adapters.js +19 -3
- package/dist/memory-flow-hud.js +8 -16
- package/dist/next-steps.js +1 -2
- package/dist/progress-port-adapter.d.ts +6 -0
- package/dist/progress-port-adapter.js +18 -0
- package/dist/public-ingest.d.ts +20 -1
- package/dist/public-ingest.js +228 -42
- package/dist/reveal-password-prompt.d.ts +24 -0
- package/dist/reveal-password-prompt.js +78 -0
- package/dist/scan.js +21 -3
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +133 -27
- package/dist/setup-databases.d.ts +18 -1
- package/dist/setup-databases.js +378 -249
- package/dist/setup-demo-tour.js +1 -0
- package/dist/setup-embeddings.js +1 -1
- package/dist/setup-models.d.ts +11 -15
- package/dist/setup-models.js +140 -276
- package/dist/setup-prompts.js +3 -2
- package/dist/setup-ready-menu.d.ts +16 -2
- package/dist/setup-ready-menu.js +37 -5
- package/dist/setup-sources.js +115 -35
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +23 -11
- package/dist/sl.d.ts +2 -2
- package/dist/sl.js +20 -4
- package/dist/sql.js +18 -2
- package/dist/star-prompt/cache.d.ts +16 -0
- package/dist/star-prompt/cache.js +45 -0
- package/dist/star-prompt/star-count.d.ts +7 -0
- package/dist/star-prompt/star-count.js +66 -0
- package/dist/star-prompt/star-line.d.ts +12 -0
- package/dist/star-prompt/star-line.js +26 -0
- package/dist/status-project.d.ts +11 -0
- package/dist/status-project.js +50 -1
- package/dist/telemetry/command-hook.d.ts +1 -0
- package/dist/telemetry/command-hook.js +3 -1
- package/dist/telemetry/emitter.d.ts +10 -0
- package/dist/telemetry/emitter.js +31 -0
- package/dist/telemetry/events.d.ts +35 -6
- package/dist/telemetry/events.js +25 -2
- package/dist/telemetry/exception.d.ts +18 -0
- package/dist/telemetry/exception.js +162 -0
- package/dist/telemetry/identity.d.ts +0 -1
- package/dist/telemetry/identity.js +6 -6
- package/dist/telemetry/index.d.ts +15 -2
- package/dist/telemetry/index.js +15 -3
- package/dist/telemetry/redaction-secrets.d.ts +11 -0
- package/dist/telemetry/redaction-secrets.js +92 -0
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/dist/update-check/cache.d.ts +21 -0
- package/dist/update-check/cache.js +38 -0
- package/dist/update-check/channel.d.ts +15 -0
- package/dist/update-check/channel.js +30 -0
- package/dist/update-check/registry.d.ts +1 -0
- package/dist/update-check/registry.js +45 -0
- package/dist/update-check/update-check.d.ts +43 -0
- package/dist/update-check/update-check.js +116 -0
- package/package.json +12 -4
- package/dist/context/connections/local-query-executor.d.ts +0 -6
- package/dist/context/connections/local-query-executor.js +0 -39
- package/dist/context/connections/postgres-query-executor.d.ts +0 -25
- package/dist/context/connections/postgres-query-executor.js +0 -53
- package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
- package/dist/context/connections/sqlite-query-executor.js +0 -74
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isAbortError, linkAbortSignal } from '../core/abort.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, counting each completed agent action so the
|
|
22
|
+
* 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
|
+
if (isActionStep && options.stepBudget !== undefined && completedSteps >= options.stepBudget) {
|
|
52
|
+
budgetExceeded = true;
|
|
53
|
+
options.abortController?.abort();
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
streamError = error instanceof Error ? error : new Error(String(error));
|
|
60
|
+
}
|
|
61
|
+
return { events: collected, budgetExceeded, ...(streamError ? { streamError } : {}) };
|
|
62
|
+
}
|
|
63
|
+
function metrics(summary, startedAt) {
|
|
64
|
+
return { totalMs: Date.now() - startedAt, usage: summary.usage };
|
|
65
|
+
}
|
|
66
|
+
function summaryError(summary, streamError) {
|
|
67
|
+
// A `turn.failed`/`error` event carries the real reason; prefer it over the
|
|
68
|
+
// SDK's generic non-zero-exit throw. Fall back to the stream error only when
|
|
69
|
+
// no event explained the failure (e.g. spawn failure or auth before a turn).
|
|
70
|
+
if (summary.error) {
|
|
71
|
+
return summary.error;
|
|
72
|
+
}
|
|
73
|
+
if (summary.toolFailures.length > 0) {
|
|
74
|
+
return new Error(`Codex runtime tool call failed: ${summary.toolFailures.join('; ')}`);
|
|
75
|
+
}
|
|
76
|
+
return streamError;
|
|
77
|
+
}
|
|
78
|
+
function assertSuccessfulText(summary, streamError) {
|
|
79
|
+
const error = summaryError(summary, streamError);
|
|
80
|
+
if (error) {
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
if (!summary.finalText.trim()) {
|
|
84
|
+
throw new Error('Codex completed without an agent message');
|
|
85
|
+
}
|
|
86
|
+
return summary.finalText;
|
|
87
|
+
}
|
|
88
|
+
function parseStructuredOutput(schema, text) {
|
|
89
|
+
try {
|
|
90
|
+
return schema.parse(JSON.parse(text));
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
throw new Error(`Codex structured output failed validation: ${message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function mcpForTools(input) {
|
|
98
|
+
if (!input.toolSet || Object.keys(input.toolSet).length === 0) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return (input.startMcpServer ?? startCodexRuntimeMcpServer)({
|
|
102
|
+
projectDir: input.projectDir,
|
|
103
|
+
toolSet: input.toolSet,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function runtimeToolNames(toolSet) {
|
|
107
|
+
return Object.values(toolSet ?? {}).map((descriptor) => descriptor.name);
|
|
108
|
+
}
|
|
109
|
+
const CODEX_RATE_LIMIT_MARKERS = /\b429\b|rate limit|too many requests|quota exceeded|temporarily overloaded/i;
|
|
110
|
+
function isCodexRateLimitError(error) {
|
|
111
|
+
return !!error && CODEX_RATE_LIMIT_MARKERS.test(error.message);
|
|
112
|
+
}
|
|
113
|
+
export class CodexKtxLlmRuntime {
|
|
114
|
+
deps;
|
|
115
|
+
runner;
|
|
116
|
+
constructor(deps) {
|
|
117
|
+
this.deps = deps;
|
|
118
|
+
this.runner = deps.runner ?? new CodexSdkCliRunner();
|
|
119
|
+
}
|
|
120
|
+
async runWithRateLimitRetry(abortSignal, run, getError) {
|
|
121
|
+
// maxRetryAttempts() returns 1 when no governor is present or pacing is
|
|
122
|
+
// disabled, so an opaque rate-limit failure surfaces on the first attempt
|
|
123
|
+
// instead of being retried with no backoff.
|
|
124
|
+
const maxAttempts = this.deps.rateLimitGovernor?.maxRetryAttempts() ?? 1;
|
|
125
|
+
for (let attempt = 0;; attempt += 1) {
|
|
126
|
+
await this.deps.rateLimitGovernor?.waitForReady(abortSignal);
|
|
127
|
+
const lastAttempt = attempt >= maxAttempts - 1;
|
|
128
|
+
try {
|
|
129
|
+
const result = await run();
|
|
130
|
+
const error = getError(result);
|
|
131
|
+
if (!isCodexRateLimitError(error) || lastAttempt) {
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (isAbortError(error)) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
140
|
+
if (!isCodexRateLimitError(err) || lastAttempt) {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.deps.rateLimitGovernor?.report({ provider: 'codex', status: 'rejected', rateLimitType: 'opaque' });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async generateText(input) {
|
|
148
|
+
const startedAt = Date.now();
|
|
149
|
+
const model = modelForRole(this.deps.modelSlots, input.role);
|
|
150
|
+
const mcp = await mcpForTools({
|
|
151
|
+
projectDir: this.deps.projectDir,
|
|
152
|
+
toolSet: input.tools,
|
|
153
|
+
startMcpServer: this.deps.startMcpServer,
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const config = buildCodexRuntimeConfig({
|
|
157
|
+
model,
|
|
158
|
+
...(mcp
|
|
159
|
+
? {
|
|
160
|
+
mcp: {
|
|
161
|
+
url: mcp.url,
|
|
162
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
163
|
+
bearerToken: mcp.bearerToken,
|
|
164
|
+
toolNames: runtimeToolNames(input.tools),
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
168
|
+
});
|
|
169
|
+
const result = await this.runWithRateLimitRetry(input.abortSignal, async () => {
|
|
170
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
171
|
+
projectDir: this.deps.projectDir,
|
|
172
|
+
model,
|
|
173
|
+
prompt: promptWithSystem(input.system, input.prompt),
|
|
174
|
+
configOverrides: config.configOverrides,
|
|
175
|
+
env: config.env,
|
|
176
|
+
...(input.abortSignal ? { signal: input.abortSignal } : {}),
|
|
177
|
+
}));
|
|
178
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
179
|
+
return { collected, summary };
|
|
180
|
+
}, ({ collected, summary }) => summaryError(summary, collected.streamError));
|
|
181
|
+
input.onMetrics?.(metrics(result.summary, startedAt));
|
|
182
|
+
return assertSuccessfulText(result.summary, result.collected.streamError);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
await mcp?.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async generateObject(input) {
|
|
189
|
+
const startedAt = Date.now();
|
|
190
|
+
const model = modelForRole(this.deps.modelSlots, input.role);
|
|
191
|
+
const mcp = await mcpForTools({
|
|
192
|
+
projectDir: this.deps.projectDir,
|
|
193
|
+
toolSet: input.tools,
|
|
194
|
+
startMcpServer: this.deps.startMcpServer,
|
|
195
|
+
});
|
|
196
|
+
try {
|
|
197
|
+
const config = buildCodexRuntimeConfig({
|
|
198
|
+
model,
|
|
199
|
+
...(mcp
|
|
200
|
+
? {
|
|
201
|
+
mcp: {
|
|
202
|
+
url: mcp.url,
|
|
203
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
204
|
+
bearerToken: mcp.bearerToken,
|
|
205
|
+
toolNames: runtimeToolNames(input.tools),
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
: {}),
|
|
209
|
+
});
|
|
210
|
+
const result = await this.runWithRateLimitRetry(input.abortSignal, async () => {
|
|
211
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
212
|
+
projectDir: this.deps.projectDir,
|
|
213
|
+
model,
|
|
214
|
+
prompt: promptWithSystem(input.system, input.prompt),
|
|
215
|
+
configOverrides: config.configOverrides,
|
|
216
|
+
env: config.env,
|
|
217
|
+
outputSchema: z.toJSONSchema(input.schema, { target: 'draft-7' }),
|
|
218
|
+
...(input.abortSignal ? { signal: input.abortSignal } : {}),
|
|
219
|
+
}));
|
|
220
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
221
|
+
return { collected, summary };
|
|
222
|
+
}, ({ collected, summary }) => summaryError(summary, collected.streamError));
|
|
223
|
+
input.onMetrics?.(metrics(result.summary, startedAt));
|
|
224
|
+
return parseStructuredOutput(input.schema, assertSuccessfulText(result.summary, result.collected.streamError));
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await mcp?.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async runAgentLoop(params) {
|
|
231
|
+
const startedAt = Date.now();
|
|
232
|
+
const model = modelForRole(this.deps.modelSlots, params.modelRole);
|
|
233
|
+
let mcp;
|
|
234
|
+
try {
|
|
235
|
+
mcp = await mcpForTools({
|
|
236
|
+
projectDir: this.deps.projectDir,
|
|
237
|
+
toolSet: params.toolSet,
|
|
238
|
+
startMcpServer: this.deps.startMcpServer,
|
|
239
|
+
});
|
|
240
|
+
const config = buildCodexRuntimeConfig({
|
|
241
|
+
model,
|
|
242
|
+
...(mcp
|
|
243
|
+
? {
|
|
244
|
+
mcp: {
|
|
245
|
+
url: mcp.url,
|
|
246
|
+
bearerTokenEnvVar: mcp.bearerTokenEnvVar,
|
|
247
|
+
bearerToken: mcp.bearerToken,
|
|
248
|
+
toolNames: runtimeToolNames(params.toolSet),
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
: {}),
|
|
252
|
+
});
|
|
253
|
+
const result = await this.runWithRateLimitRetry(params.abortSignal, async () => {
|
|
254
|
+
const linked = linkAbortSignal(params.abortSignal);
|
|
255
|
+
const abortController = linked.controller;
|
|
256
|
+
try {
|
|
257
|
+
const collected = await collectEvents(await this.runner.runStreamed({
|
|
258
|
+
projectDir: this.deps.projectDir,
|
|
259
|
+
model,
|
|
260
|
+
prompt: promptWithSystem(params.systemPrompt, params.userPrompt),
|
|
261
|
+
configOverrides: config.configOverrides,
|
|
262
|
+
env: config.env,
|
|
263
|
+
signal: abortController.signal,
|
|
264
|
+
}), { stepBudget: params.stepBudget, abortController });
|
|
265
|
+
const summary = summarizeCodexExecEvents(collected.events, { startedAt });
|
|
266
|
+
return { collected, summary };
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
linked.dispose();
|
|
270
|
+
}
|
|
271
|
+
}, ({ collected, summary }) => summaryError(summary, collected.streamError));
|
|
272
|
+
const error = summaryError(result.summary, result.collected.streamError);
|
|
273
|
+
if (isAbortError(error)) {
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
const stopReason = result.collected.budgetExceeded ? 'budget' : error ? 'error' : result.summary.stopReason;
|
|
277
|
+
return {
|
|
278
|
+
stopReason,
|
|
279
|
+
...(stopReason === 'error' && error ? { error } : {}),
|
|
280
|
+
metrics: {
|
|
281
|
+
totalMs: Date.now() - startedAt,
|
|
282
|
+
usage: result.summary.usage,
|
|
283
|
+
stepCount: result.summary.stepCount,
|
|
284
|
+
stepBoundariesMs: result.summary.stepBoundariesMs,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (isAbortError(error)) {
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
293
|
+
return {
|
|
294
|
+
stopReason: 'error',
|
|
295
|
+
error: err,
|
|
296
|
+
metrics: { totalMs: Date.now() - startedAt, usage: {}, stepCount: 0, stepBoundariesMs: [] },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
await mcp?.close();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// A rejected model is not an auth failure: Codex authenticated, connected, and
|
|
305
|
+
// the API refused the model id. These markers come from the API error envelope
|
|
306
|
+
// (e.g. "model is not supported", "invalid_request_error").
|
|
307
|
+
const MODEL_UNAVAILABLE_MARKERS = /\bnot supported\b|\bnot available\b|\bdoes not exist\b|invalid_request_error|\bunknown model\b|\bunsupported model\b/i;
|
|
308
|
+
function describeCodexProbeFailure(model, message) {
|
|
309
|
+
if (MODEL_UNAVAILABLE_MARKERS.test(message)) {
|
|
310
|
+
const fix = `Run \`codex\` to see the models your account supports, then set llm.models.default in ktx.yaml (or rerun \`ktx setup\`).`;
|
|
311
|
+
return {
|
|
312
|
+
message: `Codex is authenticated, but the configured model "${model}" is not available for this Codex account. ${fix} Details: ${message}`,
|
|
313
|
+
fix,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const fix = `Authenticate Codex locally with the Codex CLI, verify the Codex CLI is installed, then rerun setup or \`ktx status\`.`;
|
|
317
|
+
return {
|
|
318
|
+
message: `Codex authentication is not usable. ${fix} Details: ${message}`,
|
|
319
|
+
fix,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export async function runCodexAuthProbe(input) {
|
|
323
|
+
let model;
|
|
324
|
+
try {
|
|
325
|
+
model = resolveCodexModel(input.model);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
message: error instanceof Error ? error.message : String(error),
|
|
331
|
+
fix: 'Set llm.models.default in ktx.yaml to a supported codex model (codex, default, or a gpt-* / codex-* id), or rerun `ktx setup`.',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const runtime = new CodexKtxLlmRuntime({
|
|
335
|
+
projectDir: input.projectDir,
|
|
336
|
+
modelSlots: { default: model },
|
|
337
|
+
...(input.runner ? { runner: input.runner } : {}),
|
|
338
|
+
});
|
|
339
|
+
try {
|
|
340
|
+
await runtime.generateText({ role: 'default', prompt: 'Reply with exactly: ok' });
|
|
341
|
+
return { ok: true };
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
345
|
+
return { ok: false, ...describeCodexProbeFailure(model, message) };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -2,17 +2,29 @@ import { createKtxEmbeddingProvider } from '../../llm/embedding-provider.js';
|
|
|
2
2
|
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
|
+
import { AiSdkKtxLlmRuntime } from './ai-sdk-runtime.js';
|
|
5
6
|
import { ClaudeCodeKtxLlmRuntime } from './claude-code-runtime.js';
|
|
7
|
+
import { CodexKtxLlmRuntime } from './codex-runtime.js';
|
|
8
|
+
import type { RateLimitGovernor } from './rate-limit-governor.js';
|
|
6
9
|
import type { KtxLlmRuntimePort } from './runtime-port.js';
|
|
10
|
+
type ClaudeCodeRuntimeDeps = ConstructorParameters<typeof ClaudeCodeKtxLlmRuntime>[0] & {
|
|
11
|
+
rateLimitGovernor?: RateLimitGovernor;
|
|
12
|
+
};
|
|
13
|
+
type CodexRuntimeDeps = ConstructorParameters<typeof CodexKtxLlmRuntime>[0] & {
|
|
14
|
+
rateLimitGovernor?: RateLimitGovernor;
|
|
15
|
+
};
|
|
16
|
+
type AiSdkRuntimeDeps = ConstructorParameters<typeof AiSdkKtxLlmRuntime>[0] & {
|
|
17
|
+
rateLimitGovernor?: RateLimitGovernor;
|
|
18
|
+
};
|
|
7
19
|
interface LocalConfigDeps {
|
|
8
20
|
env?: NodeJS.ProcessEnv;
|
|
9
21
|
projectDir?: string;
|
|
22
|
+
rateLimitGovernor?: RateLimitGovernor;
|
|
10
23
|
createKtxLlmProvider?: typeof createKtxLlmProvider;
|
|
11
24
|
createKtxEmbeddingProvider?: typeof createKtxEmbeddingProvider;
|
|
12
|
-
createClaudeCodeRuntime?: (deps:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}) => KtxLlmRuntimePort;
|
|
25
|
+
createClaudeCodeRuntime?: (deps: ClaudeCodeRuntimeDeps) => KtxLlmRuntimePort;
|
|
26
|
+
createCodexRuntime?: (deps: CodexRuntimeDeps) => KtxLlmRuntimePort;
|
|
27
|
+
createAiSdkRuntime?: (deps: AiSdkRuntimeDeps) => KtxLlmRuntimePort;
|
|
16
28
|
}
|
|
17
29
|
export declare function resolveLocalKtxLlmConfig(config: KtxProjectLlmConfig, env: NodeJS.ProcessEnv): KtxLlmConfig | null;
|
|
18
30
|
/** @internal */
|
|
@@ -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);
|
|
@@ -89,10 +90,25 @@ export function createLocalKtxLlmRuntimeFromConfig(config, deps = {}) {
|
|
|
89
90
|
projectDir,
|
|
90
91
|
modelSlots: resolved.modelSlots,
|
|
91
92
|
env: deps.env,
|
|
93
|
+
rateLimitGovernor: deps.rateLimitGovernor,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (resolved.backend === 'codex') {
|
|
97
|
+
const projectDir = deps.projectDir;
|
|
98
|
+
if (!projectDir) {
|
|
99
|
+
throw new Error('projectDir is required when creating the codex LLM runtime');
|
|
100
|
+
}
|
|
101
|
+
return (deps.createCodexRuntime ?? ((runtimeDeps) => new CodexKtxLlmRuntime(runtimeDeps)))({
|
|
102
|
+
projectDir,
|
|
103
|
+
modelSlots: resolved.modelSlots,
|
|
104
|
+
rateLimitGovernor: deps.rateLimitGovernor,
|
|
92
105
|
});
|
|
93
106
|
}
|
|
94
107
|
const llmProvider = (deps.createKtxLlmProvider ?? createKtxLlmProvider)(resolved);
|
|
95
|
-
return (deps.createAiSdkRuntime ?? ((runtimeDeps) => new AiSdkKtxLlmRuntime(runtimeDeps)))({
|
|
108
|
+
return (deps.createAiSdkRuntime ?? ((runtimeDeps) => new AiSdkKtxLlmRuntime(runtimeDeps)))({
|
|
109
|
+
llmProvider,
|
|
110
|
+
rateLimitGovernor: deps.rateLimitGovernor,
|
|
111
|
+
});
|
|
96
112
|
}
|
|
97
113
|
export function resolveLocalKtxEmbeddingConfig(config, env) {
|
|
98
114
|
if (config.backend === 'none') {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export type RateLimitProvider = 'claude-subscription' | 'anthropic-api' | 'vertex' | 'codex';
|
|
2
|
+
type RateLimitSignalStatus = 'allowed' | 'warning' | 'rejected';
|
|
3
|
+
export interface RateLimitSignal {
|
|
4
|
+
provider: RateLimitProvider;
|
|
5
|
+
status: RateLimitSignalStatus;
|
|
6
|
+
resetAtMs?: number;
|
|
7
|
+
retryAfterMs?: number;
|
|
8
|
+
utilization?: number;
|
|
9
|
+
rateLimitType?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RateLimitRetryConfig {
|
|
12
|
+
maxAttempts: number;
|
|
13
|
+
baseDelayMs: number;
|
|
14
|
+
maxDelayMs: number;
|
|
15
|
+
jitter: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface RateLimitGovernorConfig {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
maxConcurrency: number;
|
|
20
|
+
throttleThreshold: number;
|
|
21
|
+
minConcurrencyUnderPressure: number;
|
|
22
|
+
maxWaitMs?: number;
|
|
23
|
+
waitStateTickMs: number;
|
|
24
|
+
retry: RateLimitRetryConfig;
|
|
25
|
+
}
|
|
26
|
+
export type RateLimitWaitState = {
|
|
27
|
+
kind: 'rate_limit_observed';
|
|
28
|
+
provider: RateLimitProvider;
|
|
29
|
+
status: RateLimitSignalStatus;
|
|
30
|
+
rateLimitType?: string;
|
|
31
|
+
resetAtMs?: number;
|
|
32
|
+
retryAfterMs?: number;
|
|
33
|
+
utilization?: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'concurrency_adjusted';
|
|
36
|
+
provider: RateLimitProvider;
|
|
37
|
+
from: number;
|
|
38
|
+
to: number;
|
|
39
|
+
reason: string;
|
|
40
|
+
rateLimitType?: string;
|
|
41
|
+
utilization?: number;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'wait_started' | 'wait_tick' | 'wait_finished';
|
|
44
|
+
provider: RateLimitProvider;
|
|
45
|
+
rateLimitType?: string;
|
|
46
|
+
resumeAtMs: number;
|
|
47
|
+
remainingMs: number;
|
|
48
|
+
};
|
|
49
|
+
export interface RateLimitGovernorDeps {
|
|
50
|
+
now?: () => number;
|
|
51
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
52
|
+
random?: () => number;
|
|
53
|
+
}
|
|
54
|
+
export type RateLimitRelease = () => void;
|
|
55
|
+
type Subscriber = (state: RateLimitWaitState) => void;
|
|
56
|
+
export declare function createRateLimitGovernorConfig(input?: Partial<RateLimitGovernorConfig> & {
|
|
57
|
+
retry?: Partial<RateLimitRetryConfig>;
|
|
58
|
+
}): RateLimitGovernorConfig;
|
|
59
|
+
export declare class RateLimitGovernor {
|
|
60
|
+
private readonly config;
|
|
61
|
+
private readonly now;
|
|
62
|
+
private readonly sleep;
|
|
63
|
+
private readonly random;
|
|
64
|
+
private readonly subscribers;
|
|
65
|
+
private waiters;
|
|
66
|
+
private active;
|
|
67
|
+
private effectiveLimit;
|
|
68
|
+
private pausedUntilMs;
|
|
69
|
+
private pausedProvider;
|
|
70
|
+
private pausedRateLimitType;
|
|
71
|
+
private pausedTickMs;
|
|
72
|
+
private opaqueAttempts;
|
|
73
|
+
private pauseGeneration;
|
|
74
|
+
private visibleWaitAbort;
|
|
75
|
+
constructor(config: RateLimitGovernorConfig, deps?: RateLimitGovernorDeps);
|
|
76
|
+
currentLimit(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Total attempts a runtime should make for a single rate-limited LLM call,
|
|
79
|
+
* including the first try. Returns 1 (no outer retry) when pacing is disabled:
|
|
80
|
+
* the outer retry loop only exists to cooperate with this governor's pause, so
|
|
81
|
+
* without active pacing there is no backoff to apply and the backend's own
|
|
82
|
+
* retry handles transient rejections.
|
|
83
|
+
*/
|
|
84
|
+
maxRetryAttempts(): number;
|
|
85
|
+
activeSlots(): number;
|
|
86
|
+
subscribe(cb: Subscriber): () => void;
|
|
87
|
+
report(signal: RateLimitSignal): void;
|
|
88
|
+
waitForReady(signal?: AbortSignal): Promise<void>;
|
|
89
|
+
acquireWorkSlot(signal?: AbortSignal): Promise<RateLimitRelease>;
|
|
90
|
+
private applyPause;
|
|
91
|
+
private resumeAtMsFor;
|
|
92
|
+
private adjustLimit;
|
|
93
|
+
private startVisibleWaitTicker;
|
|
94
|
+
private stopVisibleWaitTicker;
|
|
95
|
+
private runVisibleWaitTicker;
|
|
96
|
+
private finishPause;
|
|
97
|
+
private waitForPause;
|
|
98
|
+
private waitForSlot;
|
|
99
|
+
private wakeWaiters;
|
|
100
|
+
private emitWait;
|
|
101
|
+
private emit;
|
|
102
|
+
}
|
|
103
|
+
export {};
|