@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
|
@@ -44,16 +44,20 @@ type MemoryFlowEventPayload = {
|
|
|
44
44
|
percent: number;
|
|
45
45
|
message: string;
|
|
46
46
|
transient?: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'rate_limit_wait';
|
|
49
|
+
provider: string;
|
|
50
|
+
rateLimitType?: string;
|
|
51
|
+
resumeAtMs: number;
|
|
52
|
+
remainingMs: number;
|
|
47
53
|
} | {
|
|
48
54
|
type: 'work_unit_started';
|
|
49
55
|
unitKey: string;
|
|
50
56
|
skills: string[];
|
|
51
|
-
stepBudget: number;
|
|
52
57
|
} | {
|
|
53
58
|
type: 'work_unit_step';
|
|
54
59
|
unitKey: string;
|
|
55
|
-
|
|
56
|
-
stepBudget: number;
|
|
60
|
+
toolCalls: number;
|
|
57
61
|
} | {
|
|
58
62
|
type: 'candidate_action';
|
|
59
63
|
unitKey: string;
|
|
@@ -5,6 +5,7 @@ import type { KtxFileStorePort } from '../../context/core/file-store.js';
|
|
|
5
5
|
import type { KtxLogger } from '../../context/core/config.js';
|
|
6
6
|
import type { SessionOutcome } from '../../context/core/session-worktree.service.js';
|
|
7
7
|
import type { AgentRunnerPort, KtxLlmRuntimePort, KtxRuntimeToolSet } from '../../context/llm/runtime-port.js';
|
|
8
|
+
import type { RateLimitGovernor } from '../llm/rate-limit-governor.js';
|
|
8
9
|
import type { MemoryAction, MemoryKnowledgeSlRefsPort } from '../../context/memory/types.js';
|
|
9
10
|
import type { PromptService } from '../../context/prompts/prompt.service.js';
|
|
10
11
|
import type { SkillsRegistryService } from '../../context/skills/skills-registry.service.js';
|
|
@@ -111,6 +112,7 @@ interface IngestSettingsPort {
|
|
|
111
112
|
workUnitMaxConcurrency?: number;
|
|
112
113
|
workUnitStepBudget?: number;
|
|
113
114
|
workUnitFailureMode?: 'abort' | 'continue';
|
|
115
|
+
rateLimitGovernor?: RateLimitGovernor;
|
|
114
116
|
/** Print a timing breakdown to stderr at the end of each run (config-driven; see also KTX_PROFILE_INGEST). `'json'` emits the raw structured profile. */
|
|
115
117
|
profileIngest?: boolean | 'json';
|
|
116
118
|
ingestTraceLevel?: IngestTraceLevel;
|
|
@@ -282,11 +284,7 @@ export interface CuratorPaginationPort {
|
|
|
282
284
|
}) => string;
|
|
283
285
|
buildToolSet: (passNumber: number) => KtxRuntimeToolSet;
|
|
284
286
|
getReconciliationActions: () => MemoryAction[];
|
|
285
|
-
|
|
286
|
-
passNumber: number;
|
|
287
|
-
stepIndex: number;
|
|
288
|
-
stepBudget: number;
|
|
289
|
-
}) => void;
|
|
287
|
+
abortSignal?: AbortSignal;
|
|
290
288
|
}): Promise<ReconciliationOutcome & {
|
|
291
289
|
report: CuratorPaginationReport;
|
|
292
290
|
warnings: string[];
|
|
@@ -25,10 +25,7 @@ export interface WorkUnitExecutionDeps {
|
|
|
25
25
|
sourceKey: string;
|
|
26
26
|
connectionId: string;
|
|
27
27
|
jobId: string;
|
|
28
|
-
|
|
29
|
-
stepIndex: number;
|
|
30
|
-
stepBudget: number;
|
|
31
|
-
}) => void;
|
|
28
|
+
abortSignal?: AbortSignal;
|
|
32
29
|
toolFailureCount?: (unitKey: string) => number;
|
|
33
30
|
}
|
|
34
31
|
export interface WorkUnitOutcome {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAbortError } from '../../core/abort.js';
|
|
1
2
|
import { listTouchedSlSources } from '../../../context/tools/touched-sl-sources.js';
|
|
2
3
|
const MAX_WORK_UNIT_PROMPT_CHARS = 240_000;
|
|
3
4
|
export async function executeWorkUnit(deps, wu) {
|
|
@@ -51,10 +52,13 @@ export async function executeWorkUnit(deps, wu) {
|
|
|
51
52
|
unitKey: wu.unitKey,
|
|
52
53
|
jobId: deps.jobId,
|
|
53
54
|
},
|
|
54
|
-
|
|
55
|
+
abortSignal: deps.abortSignal,
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
catch (error) {
|
|
59
|
+
if (isAbortError(error)) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
58
62
|
return failWithResetFromCurrentHead(error instanceof Error ? error.message : String(error));
|
|
59
63
|
}
|
|
60
64
|
const postSha = (await deps.sessionWorktreeGit.revParseHead()) ?? preSha;
|
|
@@ -14,10 +14,7 @@ export interface ReconciliationContext {
|
|
|
14
14
|
sourceKey: string;
|
|
15
15
|
jobId: string;
|
|
16
16
|
force?: boolean;
|
|
17
|
-
|
|
18
|
-
stepIndex: number;
|
|
19
|
-
stepBudget: number;
|
|
20
|
-
}) => void;
|
|
17
|
+
abortSignal?: AbortSignal;
|
|
21
18
|
forceRun?: boolean;
|
|
22
19
|
}
|
|
23
20
|
export interface ReconciliationOutcome {
|
|
@@ -11,7 +11,7 @@ export async function runReconciliationStage4(ctx) {
|
|
|
11
11
|
toolSet: ctx.buildToolSet(),
|
|
12
12
|
stepBudget: ctx.stepBudget,
|
|
13
13
|
telemetryTags: { operationName: 'ingest-bundle-reconcile', source: ctx.sourceKey, jobId: ctx.jobId },
|
|
14
|
-
|
|
14
|
+
abortSignal: ctx.abortSignal,
|
|
15
15
|
});
|
|
16
16
|
return { skipped: false, stopReason: run.stopReason, error: run.error, ...(run.metrics ? { metrics: run.metrics } : {}) };
|
|
17
17
|
}
|
|
@@ -3,6 +3,7 @@ import { type TelemetrySettings } from 'ai';
|
|
|
3
3
|
import type { z } from 'zod';
|
|
4
4
|
import { type KtxLogger } from '../../context/core/config.js';
|
|
5
5
|
import { type KtxLlmDebugRequestRecorder } from './debug-request-recorder.js';
|
|
6
|
+
import type { RateLimitGovernor } from './rate-limit-governor.js';
|
|
6
7
|
import type { KtxGenerateObjectInput, KtxGenerateTextInput, KtxLlmRuntimePort, RunLoopParams, RunLoopResult } from './runtime-port.js';
|
|
7
8
|
interface AgentTelemetryPort {
|
|
8
9
|
createTelemetry(tags: Record<string, string>): TelemetrySettings;
|
|
@@ -12,11 +13,13 @@ export interface AiSdkKtxLlmRuntimeDeps {
|
|
|
12
13
|
telemetry?: AgentTelemetryPort;
|
|
13
14
|
logger?: KtxLogger;
|
|
14
15
|
debugRequestRecorder?: KtxLlmDebugRequestRecorder;
|
|
16
|
+
rateLimitGovernor?: Pick<RateLimitGovernor, 'waitForReady' | 'report' | 'maxRetryAttempts'>;
|
|
15
17
|
}
|
|
16
18
|
export declare class AiSdkKtxLlmRuntime implements KtxLlmRuntimePort {
|
|
17
19
|
private readonly deps;
|
|
18
20
|
private readonly logger;
|
|
19
21
|
constructor(deps: AiSdkKtxLlmRuntimeDeps);
|
|
22
|
+
private generateTextWithRateLimitRetry;
|
|
20
23
|
generateText(input: KtxGenerateTextInput): Promise<string>;
|
|
21
24
|
generateObject<TOutput, TSchema extends z.ZodType<TOutput>>(input: KtxGenerateObjectInput<TOutput, TSchema>): Promise<TOutput>;
|
|
22
25
|
runAgentLoop(params: RunLoopParams): Promise<RunLoopResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KtxMessageBuilder, splitKtxSystemMessages } from '../../llm/message-builder.js';
|
|
2
2
|
import { generateText, Output, stepCountIs } from 'ai';
|
|
3
3
|
import { noopLogger } from '../../context/core/config.js';
|
|
4
|
+
import { isAbortError } from '../core/abort.js';
|
|
4
5
|
import { summarizeKtxLlmDebugRequest } from './debug-request-recorder.js';
|
|
5
6
|
import { createAiSdkToolSet } from './runtime-tools.js';
|
|
6
7
|
function toLlmTokenUsage(usage) {
|
|
@@ -16,6 +17,108 @@ function toLlmTokenUsage(usage) {
|
|
|
16
17
|
function hasTools(tools) {
|
|
17
18
|
return Object.keys(tools).length > 0;
|
|
18
19
|
}
|
|
20
|
+
function modelProviderName(model) {
|
|
21
|
+
const provider = model.provider ?? '';
|
|
22
|
+
return provider.includes('vertex') || provider.includes('google') ? 'vertex' : 'anthropic-api';
|
|
23
|
+
}
|
|
24
|
+
const RATE_LIMIT_HEADER_PAIRS = [
|
|
25
|
+
{
|
|
26
|
+
limit: 'anthropic-ratelimit-requests-limit',
|
|
27
|
+
remaining: 'anthropic-ratelimit-requests-remaining',
|
|
28
|
+
rateLimitType: 'rpm',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
limit: 'anthropic-ratelimit-tokens-limit',
|
|
32
|
+
remaining: 'anthropic-ratelimit-tokens-remaining',
|
|
33
|
+
rateLimitType: 'tpm',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
limit: 'anthropic-ratelimit-input-tokens-limit',
|
|
37
|
+
remaining: 'anthropic-ratelimit-input-tokens-remaining',
|
|
38
|
+
rateLimitType: 'itpm',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
limit: 'anthropic-ratelimit-output-tokens-limit',
|
|
42
|
+
remaining: 'anthropic-ratelimit-output-tokens-remaining',
|
|
43
|
+
rateLimitType: 'otpm',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
limit: 'x-ratelimit-limit-requests',
|
|
47
|
+
remaining: 'x-ratelimit-remaining-requests',
|
|
48
|
+
rateLimitType: 'rpm',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
limit: 'x-ratelimit-limit-tokens',
|
|
52
|
+
remaining: 'x-ratelimit-remaining-tokens',
|
|
53
|
+
rateLimitType: 'tpm',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
function normalizeHeaders(headers) {
|
|
57
|
+
if (!headers || typeof headers !== 'object') {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
const get = headers.get;
|
|
61
|
+
if (typeof get === 'function') {
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const pair of RATE_LIMIT_HEADER_PAIRS) {
|
|
64
|
+
const limit = get.call(headers, pair.limit);
|
|
65
|
+
const remaining = get.call(headers, pair.remaining);
|
|
66
|
+
if (typeof limit === 'string')
|
|
67
|
+
out[pair.limit] = limit;
|
|
68
|
+
if (typeof remaining === 'string')
|
|
69
|
+
out[pair.remaining] = remaining;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
return Object.fromEntries(Object.entries(headers)
|
|
74
|
+
.filter((entry) => typeof entry[1] === 'string' || typeof entry[1] === 'number')
|
|
75
|
+
.map(([key, value]) => [key.toLowerCase(), String(value)]));
|
|
76
|
+
}
|
|
77
|
+
function numericHeader(headers, key) {
|
|
78
|
+
const value = Number(headers[key]);
|
|
79
|
+
return Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
function utilizationForPair(headers, pair) {
|
|
82
|
+
const limit = numericHeader(headers, pair.limit);
|
|
83
|
+
const remaining = numericHeader(headers, pair.remaining);
|
|
84
|
+
if (limit === undefined || remaining === undefined || limit <= 0) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return 1 - Math.min(limit, remaining) / limit;
|
|
88
|
+
}
|
|
89
|
+
function aiSdkHeaderRateLimitSignal(provider, result) {
|
|
90
|
+
const headers = normalizeHeaders(result.response?.headers);
|
|
91
|
+
let best;
|
|
92
|
+
for (const pair of RATE_LIMIT_HEADER_PAIRS) {
|
|
93
|
+
const utilization = utilizationForPair(headers, pair);
|
|
94
|
+
if (utilization === undefined) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!best || utilization > best.utilization) {
|
|
98
|
+
best = { utilization, rateLimitType: pair.rateLimitType };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!best) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
provider,
|
|
106
|
+
status: 'allowed',
|
|
107
|
+
rateLimitType: best.rateLimitType,
|
|
108
|
+
utilization: Number(best.utilization.toFixed(4)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function retryAfterMs(error) {
|
|
112
|
+
const value = error.retryAfter;
|
|
113
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
114
|
+
return value < 1_000 ? value * 1_000 : value;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
function isAiSdkRateLimitError(error) {
|
|
119
|
+
const record = error;
|
|
120
|
+
return record.name === 'TooManyRequestsError' || record.statusCode === 429 || record.status === 429;
|
|
121
|
+
}
|
|
19
122
|
export class AiSdkKtxLlmRuntime {
|
|
20
123
|
deps;
|
|
21
124
|
logger;
|
|
@@ -23,6 +126,37 @@ export class AiSdkKtxLlmRuntime {
|
|
|
23
126
|
this.deps = deps;
|
|
24
127
|
this.logger = deps.logger ?? noopLogger;
|
|
25
128
|
}
|
|
129
|
+
async generateTextWithRateLimitRetry(provider, abortSignal, run) {
|
|
130
|
+
// maxRetryAttempts() returns 1 when no governor is present or pacing is
|
|
131
|
+
// disabled, so a 429 throws immediately instead of hammering the provider
|
|
132
|
+
// with no backoff; the AI SDK's own maxRetries still handles transient 429s.
|
|
133
|
+
const maxAttempts = this.deps.rateLimitGovernor?.maxRetryAttempts() ?? 1;
|
|
134
|
+
let attempt = 0;
|
|
135
|
+
while (true) {
|
|
136
|
+
await this.deps.rateLimitGovernor?.waitForReady(abortSignal);
|
|
137
|
+
try {
|
|
138
|
+
const result = await run();
|
|
139
|
+
const signal = aiSdkHeaderRateLimitSignal(provider, result);
|
|
140
|
+
if (signal) {
|
|
141
|
+
this.deps.rateLimitGovernor?.report(signal);
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (isAbortError(error) || !isAiSdkRateLimitError(error) || attempt >= maxAttempts - 1) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
attempt += 1;
|
|
150
|
+
const retryAfter = retryAfterMs(error);
|
|
151
|
+
this.deps.rateLimitGovernor?.report({
|
|
152
|
+
provider,
|
|
153
|
+
status: 'rejected',
|
|
154
|
+
rateLimitType: 'http_429',
|
|
155
|
+
...(retryAfter !== undefined ? { retryAfterMs: retryAfter } : {}),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
26
160
|
async generateText(input) {
|
|
27
161
|
const model = this.deps.llmProvider.getModel(input.role);
|
|
28
162
|
if (model.provider === 'deterministic') {
|
|
@@ -37,12 +171,13 @@ export class AiSdkKtxLlmRuntime {
|
|
|
37
171
|
});
|
|
38
172
|
const split = splitKtxSystemMessages(built.messages);
|
|
39
173
|
const startedAt = Date.now();
|
|
40
|
-
const
|
|
174
|
+
const request = {
|
|
41
175
|
model,
|
|
42
176
|
temperature: input.temperature ?? 0,
|
|
43
177
|
...(split.system ? { system: split.system } : {}),
|
|
44
178
|
messages: split.messages,
|
|
45
179
|
tools: built.tools,
|
|
180
|
+
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
|
46
181
|
...(hasTools(tools)
|
|
47
182
|
? {
|
|
48
183
|
experimental_repairToolCall: this.deps.llmProvider.repairToolCallHandler({
|
|
@@ -50,7 +185,8 @@ export class AiSdkKtxLlmRuntime {
|
|
|
50
185
|
}),
|
|
51
186
|
}
|
|
52
187
|
: {}),
|
|
53
|
-
}
|
|
188
|
+
};
|
|
189
|
+
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
54
190
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
55
191
|
if (typeof result.text !== 'string') {
|
|
56
192
|
throw new Error('KTX LLM text generation returned no text');
|
|
@@ -68,12 +204,13 @@ export class AiSdkKtxLlmRuntime {
|
|
|
68
204
|
});
|
|
69
205
|
const split = splitKtxSystemMessages(built.messages);
|
|
70
206
|
const startedAt = Date.now();
|
|
71
|
-
const
|
|
207
|
+
const request = {
|
|
72
208
|
model,
|
|
73
209
|
temperature: input.temperature ?? 0,
|
|
74
210
|
...(split.system ? { system: split.system } : {}),
|
|
75
211
|
messages: split.messages,
|
|
76
212
|
tools: built.tools,
|
|
213
|
+
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
|
|
77
214
|
...(hasTools(tools)
|
|
78
215
|
? {
|
|
79
216
|
experimental_repairToolCall: this.deps.llmProvider.repairToolCallHandler({
|
|
@@ -82,7 +219,8 @@ export class AiSdkKtxLlmRuntime {
|
|
|
82
219
|
}
|
|
83
220
|
: {}),
|
|
84
221
|
output: Output.object({ schema: input.schema }),
|
|
85
|
-
}
|
|
222
|
+
};
|
|
223
|
+
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
86
224
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
87
225
|
if (result.output == null) {
|
|
88
226
|
throw new Error('KTX LLM object generation returned no output');
|
|
@@ -114,7 +252,7 @@ export class AiSdkKtxLlmRuntime {
|
|
|
114
252
|
messages: built.messages,
|
|
115
253
|
tools: built.tools,
|
|
116
254
|
}));
|
|
117
|
-
const
|
|
255
|
+
const request = {
|
|
118
256
|
model,
|
|
119
257
|
temperature: 0,
|
|
120
258
|
stopWhen: stepCountIs(params.stepBudget),
|
|
@@ -125,20 +263,15 @@ export class AiSdkKtxLlmRuntime {
|
|
|
125
263
|
...(promptMessages.system ? { system: promptMessages.system } : {}),
|
|
126
264
|
messages: promptMessages.messages,
|
|
127
265
|
tools: built.tools,
|
|
128
|
-
|
|
266
|
+
...(params.abortSignal ? { abortSignal: params.abortSignal } : {}),
|
|
267
|
+
// Count model round-trips locally for metrics. `stepCountIs(stepBudget)`
|
|
268
|
+
// caps the loop, so this counter never exceeds the budget.
|
|
269
|
+
onStepFinish: () => {
|
|
129
270
|
stepIndex += 1;
|
|
130
271
|
stepBoundariesMs.push(Date.now() - startedAt);
|
|
131
|
-
if (!params.onStepFinish) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
await params.onStepFinish({ stepIndex, stepBudget: params.stepBudget });
|
|
136
|
-
}
|
|
137
|
-
catch (err) {
|
|
138
|
-
this.logger.warn(`[agent-runner] onStepFinish callback threw; ignoring: ${err instanceof Error ? err.message : String(err)}`);
|
|
139
|
-
}
|
|
140
272
|
},
|
|
141
|
-
}
|
|
273
|
+
};
|
|
274
|
+
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), params.abortSignal, () => generateText(request));
|
|
142
275
|
return {
|
|
143
276
|
stopReason: 'natural',
|
|
144
277
|
metrics: {
|
|
@@ -150,6 +283,9 @@ export class AiSdkKtxLlmRuntime {
|
|
|
150
283
|
};
|
|
151
284
|
}
|
|
152
285
|
catch (error) {
|
|
286
|
+
if (isAbortError(error)) {
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
153
289
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
154
290
|
this.logger.warn(`[agent-runner] loop failed: ${err.message}`);
|
|
155
291
|
return {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { query as defaultQuery, type SDKMessage, type SDKResultMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import type { RateLimitGovernor } from './rate-limit-governor.js';
|
|
4
4
|
import type { KtxGenerateObjectInput, KtxGenerateTextInput, KtxLlmRuntimePort, RunLoopParams, RunLoopResult, RunLoopStopReason } from './runtime-port.js';
|
|
5
|
-
type
|
|
5
|
+
type QueryResult = AsyncIterable<SDKMessage> & {
|
|
6
|
+
interrupt?: () => void | Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
type QueryFn = (params: Parameters<typeof defaultQuery>[0]) => QueryResult;
|
|
6
9
|
export interface ClaudeCodeKtxLlmRuntimeDeps {
|
|
7
10
|
projectDir: string;
|
|
8
11
|
modelSlots: {
|
|
@@ -10,14 +13,13 @@ export interface ClaudeCodeKtxLlmRuntimeDeps {
|
|
|
10
13
|
} & Partial<Record<string, string>>;
|
|
11
14
|
query?: QueryFn;
|
|
12
15
|
env?: NodeJS.ProcessEnv;
|
|
13
|
-
|
|
16
|
+
rateLimitGovernor?: Pick<RateLimitGovernor, 'waitForReady' | 'report' | 'maxRetryAttempts'>;
|
|
14
17
|
}
|
|
15
18
|
/** @internal */
|
|
16
19
|
export declare function mapClaudeCodeStopReason(result: SDKResultMessage): RunLoopStopReason;
|
|
17
20
|
export declare class ClaudeCodeKtxLlmRuntime implements KtxLlmRuntimePort {
|
|
18
21
|
private readonly deps;
|
|
19
22
|
private readonly runQuery;
|
|
20
|
-
private readonly logger;
|
|
21
23
|
constructor(deps: ClaudeCodeKtxLlmRuntimeDeps);
|
|
22
24
|
generateText(input: KtxGenerateTextInput): Promise<string>;
|
|
23
25
|
generateObject<TOutput, TSchema extends z.ZodType<TOutput>>(input: KtxGenerateObjectInput<TOutput, TSchema>): Promise<TOutput>;
|