open-multi-agent-kit 0.97.0 → 0.98.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/CHANGELOG.md +35 -0
- package/README.md +41 -9
- package/dist/cli/args.d.ts +2 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -189
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +194 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js +2 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/resource-paths.d.ts +4 -0
- package/dist/cli/resource-paths.d.ts.map +1 -0
- package/dist/cli/resource-paths.js +59 -0
- package/dist/cli/resource-paths.js.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts +13 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.js +115 -0
- package/dist/commands/adaptorch-doctor-cli.js.map +1 -0
- package/dist/commands/resource-doctor-cli.d.ts +4 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -1
- package/dist/commands/resource-doctor-cli.js +35 -5
- package/dist/commands/resource-doctor-cli.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +11 -13
- package/dist/core/adaptorch-bridge.d.ts.map +1 -1
- package/dist/core/adaptorch-bridge.js +11 -13
- package/dist/core/adaptorch-bridge.js.map +1 -1
- package/dist/core/agent-session-snapshot.d.ts +16 -0
- package/dist/core/agent-session-snapshot.d.ts.map +1 -0
- package/dist/core/agent-session-snapshot.js +111 -0
- package/dist/core/agent-session-snapshot.js.map +1 -0
- package/dist/core/agent-session.d.ts +5 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -219
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +10 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -1
- package/dist/core/agent-tool-settings.js +10 -1
- package/dist/core/agent-tool-settings.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +7 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +25 -61
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/knowledge-triage-format.d.ts +9 -0
- package/dist/core/compaction/knowledge-triage-format.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-format.js +71 -0
- package/dist/core/compaction/knowledge-triage-format.js.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts +7 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.js +2 -0
- package/dist/core/compaction/knowledge-triage-types.js.map +1 -0
- package/dist/core/compaction/knowledge-triage.d.ts +29 -0
- package/dist/core/compaction/knowledge-triage.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage.js +189 -0
- package/dist/core/compaction/knowledge-triage.js.map +1 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +1 -1
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +4 -0
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +37 -0
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/completion-sound-io.d.ts +14 -0
- package/dist/core/completion-sound-io.d.ts.map +1 -0
- package/dist/core/completion-sound-io.js +64 -0
- package/dist/core/completion-sound-io.js.map +1 -0
- package/dist/core/completion-sound.d.ts +4 -10
- package/dist/core/completion-sound.d.ts.map +1 -1
- package/dist/core/completion-sound.js +28 -58
- package/dist/core/completion-sound.js.map +1 -1
- package/dist/core/context-budget-relevance.d.ts +15 -2
- package/dist/core/context-budget-relevance.d.ts.map +1 -1
- package/dist/core/context-budget-relevance.js +21 -9
- package/dist/core/context-budget-relevance.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-file.d.ts +23 -0
- package/dist/core/context-file.d.ts.map +1 -0
- package/dist/core/context-file.js +17 -0
- package/dist/core/context-file.js.map +1 -0
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +6 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +1 -1
- package/dist/core/model-prompt-policy.d.ts.map +1 -1
- package/dist/core/model-prompt-policy.js.map +1 -1
- package/dist/core/prompt-settlement.d.ts +6 -3
- package/dist/core/prompt-settlement.d.ts.map +1 -1
- package/dist/core/prompt-settlement.js +10 -14
- package/dist/core/prompt-settlement.js.map +1 -1
- package/dist/core/reasoning-router-promotion.d.ts +41 -1
- package/dist/core/reasoning-router-promotion.d.ts.map +1 -1
- package/dist/core/reasoning-router-promotion.js +31 -2
- package/dist/core/reasoning-router-promotion.js.map +1 -1
- package/dist/core/reasoning-router-replay-stability.d.ts +65 -0
- package/dist/core/reasoning-router-replay-stability.d.ts.map +1 -0
- package/dist/core/reasoning-router-replay-stability.js +67 -0
- package/dist/core/reasoning-router-replay-stability.js.map +1 -0
- package/dist/core/reasoning-router-v4-weights.d.ts +6 -6
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4-weights.js +3 -3
- package/dist/core/reasoning-router-v4-weights.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +6 -6
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +1 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/resource-admission.d.ts +2 -1
- package/dist/core/resource-admission.d.ts.map +1 -1
- package/dist/core/resource-admission.js +11 -0
- package/dist/core/resource-admission.js.map +1 -1
- package/dist/core/resource-governor-format.d.ts +1 -1
- package/dist/core/resource-governor-format.d.ts.map +1 -1
- package/dist/core/resource-governor-format.js.map +1 -1
- package/dist/core/resource-governor-settings.d.ts +6 -12
- package/dist/core/resource-governor-settings.d.ts.map +1 -1
- package/dist/core/resource-governor-settings.js.map +1 -1
- package/dist/core/resource-governor-types.d.ts +24 -0
- package/dist/core/resource-governor-types.d.ts.map +1 -0
- package/dist/core/resource-governor-types.js +15 -0
- package/dist/core/resource-governor-types.js.map +1 -0
- package/dist/core/resource-loader.d.ts +1 -7
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +2 -1
- package/dist/core/resource-observation-journal.d.ts.map +1 -1
- package/dist/core/resource-observation-journal.js +4 -3
- package/dist/core/resource-observation-journal.js.map +1 -1
- package/dist/core/resource-observation-report-reader.d.ts +7 -0
- package/dist/core/resource-observation-report-reader.d.ts.map +1 -0
- package/dist/core/resource-observation-report-reader.js +157 -0
- package/dist/core/resource-observation-report-reader.js.map +1 -0
- package/dist/core/resource-observation-report.d.ts +24 -0
- package/dist/core/resource-observation-report.d.ts.map +1 -0
- package/dist/core/resource-observation-report.js +101 -0
- package/dist/core/resource-observation-report.js.map +1 -0
- package/dist/core/router-feedback-collector.d.ts +1 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/session-failure-cause.d.ts +33 -0
- package/dist/core/session-failure-cause.d.ts.map +1 -0
- package/dist/core/session-failure-cause.js +141 -0
- package/dist/core/session-failure-cause.js.map +1 -0
- package/dist/core/session-system-prompt.d.ts +1 -1
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +7 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +2 -10
- package/dist/core/workload-classifier.d.ts.map +1 -1
- package/dist/core/workload-classifier.js +17 -0
- package/dist/core/workload-classifier.js.map +1 -1
- package/dist/core/workload-family-matcher.d.ts +1 -1
- package/dist/core/workload-family-matcher.d.ts.map +1 -1
- package/dist/core/workload-family-matcher.js.map +1 -1
- package/dist/core/workload-shard-execution-types.d.ts +40 -0
- package/dist/core/workload-shard-execution-types.d.ts.map +1 -0
- package/dist/core/workload-shard-execution-types.js +14 -0
- package/dist/core/workload-shard-execution-types.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +3 -27
- package/dist/core/workload-shard-executor.d.ts.map +1 -1
- package/dist/core/workload-shard-executor.js.map +1 -1
- package/dist/core/workload-shard-runner.d.ts +1 -1
- package/dist/core/workload-shard-runner.d.ts.map +1 -1
- package/dist/core/workload-shard-runner.js.map +1 -1
- package/dist/core/workload-types.d.ts +24 -0
- package/dist/core/workload-types.d.ts.map +1 -0
- package/dist/core/workload-types.js +15 -0
- package/dist/core/workload-types.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +27 -47
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/theme/omk-control-panel.json +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +42 -33
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +14 -2
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +20 -18
- package/docs/adaptorch-preview.md +9 -9
- package/docs/compaction.md +24 -3
- package/docs/correctness-wall.md +10 -10
- package/docs/docs.json +12 -0
- package/docs/environment-variables.md +9 -2
- package/docs/index.md +3 -0
- package/docs/runtime-algorithms.md +359 -0
- package/docs/sdk.md +2 -2
- package/docs/settings.md +11 -10
- package/docs/usage.md +18 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -6
- package/dist/core/context-budget-governor.d.ts +0 -67
- package/dist/core/context-budget-governor.d.ts.map +0 -1
- package/dist/core/context-budget-governor.js +0 -224
- package/dist/core/context-budget-governor.js.map +0 -1
- package/dist/core/lean-ctx.d.ts +0 -99
- package/dist/core/lean-ctx.d.ts.map +0 -1
- package/dist/core/lean-ctx.js +0 -225
- package/dist/core/lean-ctx.js.map +0 -1
- package/dist/core/read-anchors.d.ts +0 -110
- package/dist/core/read-anchors.d.ts.map +0 -1
- package/dist/core/read-anchors.js +0 -193
- package/dist/core/read-anchors.js.map +0 -1
- package/dist/core/recovery-checkpoint.d.ts +0 -196
- package/dist/core/recovery-checkpoint.d.ts.map +0 -1
- package/dist/core/recovery-checkpoint.js +0 -382
- package/dist/core/recovery-checkpoint.js.map +0 -1
- package/dist/core/sandbox-policy-v2.d.ts +0 -49
- package/dist/core/sandbox-policy-v2.d.ts.map +0 -1
- package/dist/core/sandbox-policy-v2.js +0 -368
- package/dist/core/sandbox-policy-v2.js.map +0 -1
- package/dist/guardrails/audit-logger.d.ts +0 -4
- package/dist/guardrails/audit-logger.d.ts.map +0 -1
- package/dist/guardrails/audit-logger.js +0 -28
- package/dist/guardrails/audit-logger.js.map +0 -1
- package/dist/guardrails/freedomd-adapter.d.ts +0 -16
- package/dist/guardrails/freedomd-adapter.d.ts.map +0 -1
- package/dist/guardrails/freedomd-adapter.js +0 -87
- package/dist/guardrails/freedomd-adapter.js.map +0 -1
- package/dist/orchestration/lane-grant-auditor.d.ts +0 -4
- package/dist/orchestration/lane-grant-auditor.d.ts.map +0 -1
- package/dist/orchestration/lane-grant-auditor.js +0 -87
- package/dist/orchestration/lane-grant-auditor.js.map +0 -1
- package/dist/types/guardrails.d.ts +0 -61
- package/dist/types/guardrails.d.ts.map +0 -1
- package/dist/types/guardrails.js +0 -9
- package/dist/types/guardrails.js.map +0 -1
- package/dist/types/lane-grant.d.ts +0 -58
- package/dist/types/lane-grant.d.ts.map +0 -1
- package/dist/types/lane-grant.js +0 -9
- package/dist/types/lane-grant.js.map +0 -1
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-multi-agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.98.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "open-multi-agent-kit",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.98.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"omk-agent-core": "^0.
|
|
13
|
-
"omk-ai": "^0.
|
|
14
|
-
"omk-tui": "^0.
|
|
12
|
+
"omk-agent-core": "^0.98.0",
|
|
13
|
+
"omk-ai": "^0.98.0",
|
|
14
|
+
"omk-tui": "^0.98.0",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"canonicalize": "4.0.0",
|
|
17
17
|
"chalk": "5.6.2",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"typebox": "1.3.11",
|
|
28
28
|
"undici": "8.10.0",
|
|
29
29
|
"yaml": "2.9.0",
|
|
30
|
-
"omk-adaptorch-wpl": "^0.
|
|
31
|
-
"omk-protocol": "^0.
|
|
30
|
+
"omk-adaptorch-wpl": "^0.98.0",
|
|
31
|
+
"omk-protocol": "^0.98.0"
|
|
32
32
|
},
|
|
33
33
|
"optionalDependencies": {
|
|
34
34
|
"@mariozechner/clipboard": "0.3.9"
|
|
@@ -1285,19 +1285,19 @@
|
|
|
1285
1285
|
}
|
|
1286
1286
|
},
|
|
1287
1287
|
"node_modules/omk-adaptorch-wpl": {
|
|
1288
|
-
"version": "0.
|
|
1289
|
-
"resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.
|
|
1288
|
+
"version": "0.98.0",
|
|
1289
|
+
"resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.98.0.tgz",
|
|
1290
1290
|
"license": "MIT",
|
|
1291
1291
|
"engines": {
|
|
1292
1292
|
"node": ">=22.19.0"
|
|
1293
1293
|
}
|
|
1294
1294
|
},
|
|
1295
1295
|
"node_modules/omk-agent-core": {
|
|
1296
|
-
"version": "0.
|
|
1297
|
-
"resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.
|
|
1296
|
+
"version": "0.98.0",
|
|
1297
|
+
"resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.98.0.tgz",
|
|
1298
1298
|
"license": "MIT",
|
|
1299
1299
|
"dependencies": {
|
|
1300
|
-
"omk-ai": "^0.
|
|
1300
|
+
"omk-ai": "^0.98.0",
|
|
1301
1301
|
"ignore": "7.0.6",
|
|
1302
1302
|
"typebox": "1.3.11",
|
|
1303
1303
|
"yaml": "2.9.0"
|
|
@@ -1307,8 +1307,8 @@
|
|
|
1307
1307
|
}
|
|
1308
1308
|
},
|
|
1309
1309
|
"node_modules/omk-ai": {
|
|
1310
|
-
"version": "0.
|
|
1311
|
-
"resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.
|
|
1310
|
+
"version": "0.98.0",
|
|
1311
|
+
"resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.98.0.tgz",
|
|
1312
1312
|
"license": "MIT",
|
|
1313
1313
|
"dependencies": {
|
|
1314
1314
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -1330,16 +1330,16 @@
|
|
|
1330
1330
|
}
|
|
1331
1331
|
},
|
|
1332
1332
|
"node_modules/omk-protocol": {
|
|
1333
|
-
"version": "0.
|
|
1334
|
-
"resolved": "https://registry.npmjs.org/omk-protocol/-/omk-protocol-0.
|
|
1333
|
+
"version": "0.98.0",
|
|
1334
|
+
"resolved": "https://registry.npmjs.org/omk-protocol/-/omk-protocol-0.98.0.tgz",
|
|
1335
1335
|
"license": "MIT",
|
|
1336
1336
|
"engines": {
|
|
1337
1337
|
"node": ">=22.19.0"
|
|
1338
1338
|
}
|
|
1339
1339
|
},
|
|
1340
1340
|
"node_modules/omk-tui": {
|
|
1341
|
-
"version": "0.
|
|
1342
|
-
"resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.
|
|
1341
|
+
"version": "0.98.0",
|
|
1342
|
+
"resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.98.0.tgz",
|
|
1343
1343
|
"license": "MIT",
|
|
1344
1344
|
"dependencies": {
|
|
1345
1345
|
"get-east-asian-width": "1.6.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-multi-agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.98.0",
|
|
4
4
|
"description": "OMK (Open Multi-Agent Kit) coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"omkConfig": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"link-workspace-deps": "node ./scripts/link-workspace-deps.mjs"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"omk-agent-core": "^0.
|
|
46
|
-
"omk-ai": "^0.
|
|
47
|
-
"omk-tui": "^0.
|
|
45
|
+
"omk-agent-core": "^0.98.0",
|
|
46
|
+
"omk-ai": "^0.98.0",
|
|
47
|
+
"omk-tui": "^0.98.0",
|
|
48
48
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
49
49
|
"canonicalize": "4.0.0",
|
|
50
50
|
"chalk": "5.6.2",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"typebox": "1.3.11",
|
|
61
61
|
"undici": "8.10.0",
|
|
62
62
|
"yaml": "2.9.0",
|
|
63
|
-
"omk-adaptorch-wpl": "^0.
|
|
64
|
-
"omk-protocol": "^0.
|
|
63
|
+
"omk-adaptorch-wpl": "^0.98.0",
|
|
64
|
+
"omk-protocol": "^0.98.0"
|
|
65
65
|
},
|
|
66
66
|
"overrides": {
|
|
67
67
|
"rimraf": "6.1.2",
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { type TokenCounterAdapter } from "./context-budget-token-counter.ts";
|
|
2
|
-
export type ContextBudgetItemKind = "system" | "context-pointer" | "context-full" | "skill-header" | "skill" | "mcp" | "history" | "tool-result" | "subagent-evidence" | "budget-note";
|
|
3
|
-
export type ContextBudgetPriority = "hard" | "high" | "medium" | "low";
|
|
4
|
-
export type ContextBudgetDiagnosticReason = "hard_pin_over_capacity" | "item_omitted" | "invalid_budget";
|
|
5
|
-
export interface ContextBudgetItem {
|
|
6
|
-
readonly id: string;
|
|
7
|
-
readonly kind: ContextBudgetItemKind;
|
|
8
|
-
readonly text: string;
|
|
9
|
-
readonly priority: ContextBudgetPriority;
|
|
10
|
-
readonly tokenEstimate?: number;
|
|
11
|
-
readonly relevance?: number;
|
|
12
|
-
readonly recency?: number;
|
|
13
|
-
readonly evidenceValue?: number;
|
|
14
|
-
readonly redundancyKey?: string;
|
|
15
|
-
}
|
|
16
|
-
export interface ContextBudgetGovernorInput {
|
|
17
|
-
readonly maxTokens: number;
|
|
18
|
-
readonly responseReserveTokens?: number;
|
|
19
|
-
readonly modelId?: string;
|
|
20
|
-
readonly policyVersion?: string;
|
|
21
|
-
readonly items: readonly ContextBudgetItem[];
|
|
22
|
-
readonly tokenCounter?: TokenCounterAdapter;
|
|
23
|
-
}
|
|
24
|
-
export interface ContextBudgetPlannedItem extends ContextBudgetItem {
|
|
25
|
-
readonly estimatedTokens: number;
|
|
26
|
-
readonly score: number;
|
|
27
|
-
}
|
|
28
|
-
export interface ContextBudgetDiagnostic {
|
|
29
|
-
readonly reason: ContextBudgetDiagnosticReason;
|
|
30
|
-
readonly itemId?: string;
|
|
31
|
-
readonly detail: string;
|
|
32
|
-
}
|
|
33
|
-
export interface ContextBudgetObservabilityCounts {
|
|
34
|
-
readonly selected: number;
|
|
35
|
-
readonly omitted: number;
|
|
36
|
-
}
|
|
37
|
-
export interface ContextBudgetObservabilityTokens {
|
|
38
|
-
readonly available: number;
|
|
39
|
-
readonly used: number;
|
|
40
|
-
readonly omitted: number;
|
|
41
|
-
readonly tokenSavings: number;
|
|
42
|
-
}
|
|
43
|
-
export interface ContextBudgetObservability {
|
|
44
|
-
readonly counts: ContextBudgetObservabilityCounts;
|
|
45
|
-
readonly diagnosticReasons: readonly ContextBudgetDiagnosticReason[];
|
|
46
|
-
readonly tokens: ContextBudgetObservabilityTokens;
|
|
47
|
-
readonly planHash: string;
|
|
48
|
-
}
|
|
49
|
-
export interface ContextBudgetPlan {
|
|
50
|
-
readonly policyVersion: string;
|
|
51
|
-
readonly maxTokens: number;
|
|
52
|
-
readonly responseReserveTokens: number;
|
|
53
|
-
readonly availableTokens: number;
|
|
54
|
-
readonly usedTokens: number;
|
|
55
|
-
readonly omittedTokens: number;
|
|
56
|
-
readonly emergency: boolean;
|
|
57
|
-
readonly omittedHighPriority: boolean;
|
|
58
|
-
readonly includedItems: readonly ContextBudgetPlannedItem[];
|
|
59
|
-
readonly omittedItems: readonly ContextBudgetPlannedItem[];
|
|
60
|
-
readonly diagnostics: readonly ContextBudgetDiagnostic[];
|
|
61
|
-
readonly planHash: string;
|
|
62
|
-
readonly observability: ContextBudgetObservability;
|
|
63
|
-
}
|
|
64
|
-
export declare const CONTEXT_BUDGET_POLICY_VERSION = "context-budget-v1";
|
|
65
|
-
export declare function planContextBudget(input: ContextBudgetGovernorInput): ContextBudgetPlan;
|
|
66
|
-
export declare function scoreContextBudgetItem(item: ContextBudgetItem, estimatedTokens: number): number;
|
|
67
|
-
//# sourceMappingURL=context-budget-governor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-governor.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-governor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { createFallbackTokenCounter, type TokenCounterAdapter } from \"./context-budget-token-counter.ts\";\n\nexport type ContextBudgetItemKind =\n\t| \"system\"\n\t| \"context-pointer\"\n\t| \"context-full\"\n\t| \"skill-header\"\n\t| \"skill\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"tool-result\"\n\t| \"subagent-evidence\"\n\t| \"budget-note\";\n\nexport type ContextBudgetPriority = \"hard\" | \"high\" | \"medium\" | \"low\";\nexport type ContextBudgetDiagnosticReason = \"hard_pin_over_capacity\" | \"item_omitted\" | \"invalid_budget\";\n\nexport interface ContextBudgetItem {\n\treadonly id: string;\n\treadonly kind: ContextBudgetItemKind;\n\treadonly text: string;\n\treadonly priority: ContextBudgetPriority;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly redundancyKey?: string;\n}\n\nexport interface ContextBudgetGovernorInput {\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens?: number;\n\treadonly modelId?: string;\n\treadonly policyVersion?: string;\n\treadonly items: readonly ContextBudgetItem[];\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nexport interface ContextBudgetPlannedItem extends ContextBudgetItem {\n\treadonly estimatedTokens: number;\n\treadonly score: number;\n}\n\nexport interface ContextBudgetDiagnostic {\n\treadonly reason: ContextBudgetDiagnosticReason;\n\treadonly itemId?: string;\n\treadonly detail: string;\n}\n\nexport interface ContextBudgetObservabilityCounts {\n\treadonly selected: number;\n\treadonly omitted: number;\n}\n\nexport interface ContextBudgetObservabilityTokens {\n\treadonly available: number;\n\treadonly used: number;\n\treadonly omitted: number;\n\treadonly tokenSavings: number;\n}\n\nexport interface ContextBudgetObservability {\n\treadonly counts: ContextBudgetObservabilityCounts;\n\treadonly diagnosticReasons: readonly ContextBudgetDiagnosticReason[];\n\treadonly tokens: ContextBudgetObservabilityTokens;\n\treadonly planHash: string;\n}\n\nexport interface ContextBudgetPlan {\n\treadonly policyVersion: string;\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens: number;\n\treadonly availableTokens: number;\n\treadonly usedTokens: number;\n\treadonly omittedTokens: number;\n\treadonly emergency: boolean;\n\treadonly omittedHighPriority: boolean;\n\treadonly includedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedItems: readonly ContextBudgetPlannedItem[];\n\treadonly diagnostics: readonly ContextBudgetDiagnostic[];\n\treadonly planHash: string;\n\treadonly observability: ContextBudgetObservability;\n}\n\nexport const CONTEXT_BUDGET_POLICY_VERSION = \"context-budget-v1\";\n\nexport function planContextBudget(input: ContextBudgetGovernorInput): ContextBudgetPlan {\n\tconst tokenCounter = input.tokenCounter ?? createFallbackTokenCounter();\n\tconst modelId = input.modelId ?? \"unknown\";\n\tconst diagnostics: ContextBudgetDiagnostic[] = [];\n\tconst maxTokens = normalizeBudgetTokens(\"maxTokens\", input.maxTokens, diagnostics);\n\tconst responseReserveTokens = normalizeBudgetTokens(\n\t\t\"responseReserveTokens\",\n\t\tinput.responseReserveTokens ?? 0,\n\t\tdiagnostics,\n\t);\n\tconst availableTokens = Math.max(0, maxTokens - responseReserveTokens);\n\tconst plannedItems = input.items.map((item) => planItem(item, tokenCounter, modelId, diagnostics));\n\tconst includedIds = new Set<string>();\n\tlet usedTokens = 0;\n\n\tfor (const item of plannedItems) {\n\t\tif (item.priority !== \"hard\") {\n\t\t\tcontinue;\n\t\t}\n\t\tincludedIds.add(item.id);\n\t\tusedTokens += item.estimatedTokens;\n\t}\n\n\tif (usedTokens > availableTokens) {\n\t\tdiagnostics.push({\n\t\t\treason: \"hard_pin_over_capacity\",\n\t\t\tdetail: `hard-pinned context uses ${usedTokens} tokens against ${availableTokens} available tokens`,\n\t\t});\n\t}\n\n\tconst selectedRedundancyKeys = new Set<string>();\n\tconst remainingOptionalItems = plannedItems.filter((item) => item.priority !== \"hard\");\n\n\twhile (remainingOptionalItems.length > 0) {\n\t\tconst rankedItems = remainingOptionalItems\n\t\t\t.map((item) => applyRedundancyPenalty(item, selectedRedundancyKeys))\n\t\t\t.sort(comparePlannedItemsForSelection);\n\t\tconst selected = rankedItems.find((item) => usedTokens + item.estimatedTokens <= availableTokens);\n\t\tif (!selected) {\n\t\t\tbreak;\n\t\t}\n\n\t\tincludedIds.add(selected.id);\n\t\tusedTokens += selected.estimatedTokens;\n\t\tif (selected.redundancyKey) {\n\t\t\tselectedRedundancyKeys.add(selected.redundancyKey);\n\t\t}\n\n\t\tconst selectedIndex = remainingOptionalItems.findIndex((item) => item.id === selected.id);\n\t\tremainingOptionalItems.splice(selectedIndex, 1);\n\t}\n\n\tconst includedItems = plannedItems.filter((item) => includedIds.has(item.id));\n\tconst omittedItems = plannedItems.filter((item) => !includedIds.has(item.id));\n\tfor (const item of omittedItems) {\n\t\tdiagnostics.push({ reason: \"item_omitted\", itemId: item.id, detail: `${item.kind} omitted from context budget` });\n\t}\n\tconst omittedTokens = omittedItems.reduce((sum, item) => sum + item.estimatedTokens, 0);\n\tconst omittedHighPriority = omittedItems.some((item) => item.priority === \"high\");\n\tconst emergency =\n\t\tusedTokens > availableTokens || diagnostics.some((diagnostic) => diagnostic.reason === \"invalid_budget\");\n\tconst policyVersion = input.policyVersion ?? CONTEXT_BUDGET_POLICY_VERSION;\n\n\tconst planWithoutHash = {\n\t\tpolicyVersion,\n\t\tmaxTokens,\n\t\tresponseReserveTokens,\n\t\tavailableTokens,\n\t\tusedTokens,\n\t\tomittedTokens,\n\t\temergency,\n\t\tomittedHighPriority,\n\t\tincluded: includedItems.map((item) => [item.id, item.estimatedTokens, item.score]),\n\t\tomitted: omittedItems.map((item) => [item.id, item.estimatedTokens, item.score]),\n\t};\n\tconst planHash = createHash(\"sha256\").update(JSON.stringify(planWithoutHash), \"utf8\").digest(\"hex\");\n\tconst observability = buildObservability({\n\t\tavailableTokens,\n\t\tdiagnostics,\n\t\tincludedItems,\n\t\tomittedItems,\n\t\tomittedTokens,\n\t\tplanHash,\n\t\tusedTokens,\n\t});\n\n\treturn {\n\t\tpolicyVersion,\n\t\tmaxTokens,\n\t\tresponseReserveTokens,\n\t\tavailableTokens,\n\t\tusedTokens,\n\t\tomittedTokens,\n\t\temergency,\n\t\tomittedHighPriority,\n\t\tincludedItems,\n\t\tomittedItems,\n\t\tdiagnostics,\n\t\tplanHash,\n\t\tobservability,\n\t};\n}\n\nexport function scoreContextBudgetItem(item: ContextBudgetItem, estimatedTokens: number): number {\n\tconst priorityScore = priorityWeight(item.priority);\n\tconst relevance = clampScore(item.relevance) * 20;\n\tconst recency = clampScore(item.recency) * 10;\n\tconst evidence = clampScore(item.evidenceValue) * 15;\n\t// G1: costPenalty 비례화 — sqrt 기반으로 대항목에 더 큰 불이익 부여.\n\t// log2(t)*2는 1000토큰에서 ~19.9점으로 relevance(20점)와 동급 → 대항목 생존.\n\t// sqrt(t)*0.5는 1000토큰 15.8점, 5000토큰 35.4점, 10000토큰 50점 → 비례적 증가.\n\tconst costPenalty = Math.sqrt(normalizeTokenCount(estimatedTokens)) * 0.5;\n\treturn priorityScore + relevance + recency + evidence - costPenalty;\n}\n\nfunction planItem(\n\titem: ContextBudgetItem,\n\ttokenCounter: TokenCounterAdapter,\n\tmodelId: string,\n\tdiagnostics: ContextBudgetDiagnostic[],\n): ContextBudgetPlannedItem {\n\tconst rawEstimate = item.tokenEstimate ?? tokenCounter.countText(item.text, modelId).tokens;\n\tconst estimatedTokens = normalizeItemTokenEstimate(item.id, rawEstimate, diagnostics);\n\treturn {\n\t\t...item,\n\t\testimatedTokens,\n\t\tscore: item.priority === \"hard\" ? Number.POSITIVE_INFINITY : scoreContextBudgetItem(item, estimatedTokens),\n\t};\n}\n\nconst REDUNDANCY_PENALTY_FLOOR = 10;\nconst REDUNDANCY_PENALTY_SCALE = 0.8;\n\n/**\n * W4: Adaptive redundancy penalty — proportional to token count.\n * Large duplicates waste more budget so deserve higher penalty.\n * Formula: max(floor, sqrt(tokens) * scale)\n * - 200 tokens → 11.3 (near floor — minimal waste)\n * - 1000 tokens → 25.3 (moderate waste)\n * - 5000 tokens → 56.6 (heavy waste — strongly penalized)\n * Floor ensures small duplicates are still penalized meaningfully.\n * Scale (0.8) tuned to score medium=50 so large duplicates go negative.\n */\nfunction redundancyPenalty(estimatedTokens: number): number {\n\treturn Math.max(REDUNDANCY_PENALTY_FLOOR, Math.sqrt(estimatedTokens) * REDUNDANCY_PENALTY_SCALE);\n}\n\nfunction applyRedundancyPenalty(\n\titem: ContextBudgetPlannedItem,\n\tselectedRedundancyKeys: Set<string>,\n): ContextBudgetPlannedItem {\n\tif (!item.redundancyKey || !selectedRedundancyKeys.has(item.redundancyKey)) {\n\t\treturn item;\n\t}\n\treturn { ...item, score: item.score - redundancyPenalty(item.estimatedTokens) };\n}\n\nfunction comparePlannedItemsForSelection(a: ContextBudgetPlannedItem, b: ContextBudgetPlannedItem): number {\n\t// G2: 우선순위 티어 내부에서 밀도(score/tokens) 기반 정렬.\n\t// 단위 토큰당 가치가 높은 항목을 우선 선택 → 예산 활용률 극대화.\n\tconst aTier = priorityTier(a.priority);\n\tconst bTier = priorityTier(b.priority);\n\tif (aTier !== bTier) {\n\t\treturn aTier - bTier;\n\t}\n\tconst aDensity = a.score > 0 ? a.score / Math.max(a.estimatedTokens, 1) : 0;\n\tconst bDensity = b.score > 0 ? b.score / Math.max(b.estimatedTokens, 1) : 0;\n\tif (aDensity !== bDensity) {\n\t\treturn bDensity - aDensity;\n\t}\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.id.localeCompare(b.id);\n}\n\nfunction priorityTier(priority: ContextBudgetPriority): number {\n\tswitch (priority) {\n\t\tcase \"hard\":\n\t\t\treturn 0;\n\t\tcase \"high\":\n\t\t\treturn 1;\n\t\tcase \"medium\":\n\t\t\treturn 2;\n\t\tcase \"low\":\n\t\t\treturn 3;\n\t}\n}\n\nfunction priorityWeight(priority: ContextBudgetPriority): number {\n\tswitch (priority) {\n\t\tcase \"hard\":\n\t\t\treturn 1_000_000;\n\t\tcase \"high\":\n\t\t\treturn 100;\n\t\tcase \"medium\":\n\t\t\treturn 50;\n\t\tcase \"low\":\n\t\t\treturn 10;\n\t}\n}\n\nfunction clampScore(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\treturn Math.max(0, Math.min(1, value));\n}\n\nfunction normalizeBudgetTokens(\n\tfield: \"maxTokens\" | \"responseReserveTokens\",\n\tvalue: number,\n\tdiagnostics: ContextBudgetDiagnostic[],\n): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tdiagnostics.push({\n\t\t\treason: \"invalid_budget\",\n\t\t\tdetail: `${field} must be a non-negative finite number, got ${value}`,\n\t\t});\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction normalizeItemTokenEstimate(itemId: string, value: number, diagnostics: ContextBudgetDiagnostic[]): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tdiagnostics.push({\n\t\t\treason: \"invalid_budget\",\n\t\t\titemId,\n\t\t\tdetail: `tokenEstimate must be a non-negative finite number, got ${value}`,\n\t\t});\n\t\treturn 0;\n\t}\n\treturn Math.ceil(value);\n}\n\nfunction normalizeTokenCount(value: number): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\treturn 0;\n\t}\n\treturn value;\n}\n\nfunction buildObservability(input: {\n\treadonly availableTokens: number;\n\treadonly diagnostics: readonly ContextBudgetDiagnostic[];\n\treadonly includedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedTokens: number;\n\treadonly planHash: string;\n\treadonly usedTokens: number;\n}): ContextBudgetObservability {\n\treturn {\n\t\tcounts: {\n\t\t\tselected: input.includedItems.length,\n\t\t\tomitted: input.omittedItems.length,\n\t\t},\n\t\tdiagnosticReasons: [...new Set(input.diagnostics.map((diagnostic) => diagnostic.reason))].sort(),\n\t\ttokens: {\n\t\t\tavailable: input.availableTokens,\n\t\t\tused: input.usedTokens,\n\t\t\tomitted: input.omittedTokens,\n\t\t\ttokenSavings: input.omittedTokens,\n\t\t},\n\t\tplanHash: input.planHash,\n\t};\n}\n"]}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { createFallbackTokenCounter } from "./context-budget-token-counter.js";
|
|
3
|
-
export const CONTEXT_BUDGET_POLICY_VERSION = "context-budget-v1";
|
|
4
|
-
export function planContextBudget(input) {
|
|
5
|
-
const tokenCounter = input.tokenCounter ?? createFallbackTokenCounter();
|
|
6
|
-
const modelId = input.modelId ?? "unknown";
|
|
7
|
-
const diagnostics = [];
|
|
8
|
-
const maxTokens = normalizeBudgetTokens("maxTokens", input.maxTokens, diagnostics);
|
|
9
|
-
const responseReserveTokens = normalizeBudgetTokens("responseReserveTokens", input.responseReserveTokens ?? 0, diagnostics);
|
|
10
|
-
const availableTokens = Math.max(0, maxTokens - responseReserveTokens);
|
|
11
|
-
const plannedItems = input.items.map((item) => planItem(item, tokenCounter, modelId, diagnostics));
|
|
12
|
-
const includedIds = new Set();
|
|
13
|
-
let usedTokens = 0;
|
|
14
|
-
for (const item of plannedItems) {
|
|
15
|
-
if (item.priority !== "hard") {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
includedIds.add(item.id);
|
|
19
|
-
usedTokens += item.estimatedTokens;
|
|
20
|
-
}
|
|
21
|
-
if (usedTokens > availableTokens) {
|
|
22
|
-
diagnostics.push({
|
|
23
|
-
reason: "hard_pin_over_capacity",
|
|
24
|
-
detail: `hard-pinned context uses ${usedTokens} tokens against ${availableTokens} available tokens`,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
const selectedRedundancyKeys = new Set();
|
|
28
|
-
const remainingOptionalItems = plannedItems.filter((item) => item.priority !== "hard");
|
|
29
|
-
while (remainingOptionalItems.length > 0) {
|
|
30
|
-
const rankedItems = remainingOptionalItems
|
|
31
|
-
.map((item) => applyRedundancyPenalty(item, selectedRedundancyKeys))
|
|
32
|
-
.sort(comparePlannedItemsForSelection);
|
|
33
|
-
const selected = rankedItems.find((item) => usedTokens + item.estimatedTokens <= availableTokens);
|
|
34
|
-
if (!selected) {
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
includedIds.add(selected.id);
|
|
38
|
-
usedTokens += selected.estimatedTokens;
|
|
39
|
-
if (selected.redundancyKey) {
|
|
40
|
-
selectedRedundancyKeys.add(selected.redundancyKey);
|
|
41
|
-
}
|
|
42
|
-
const selectedIndex = remainingOptionalItems.findIndex((item) => item.id === selected.id);
|
|
43
|
-
remainingOptionalItems.splice(selectedIndex, 1);
|
|
44
|
-
}
|
|
45
|
-
const includedItems = plannedItems.filter((item) => includedIds.has(item.id));
|
|
46
|
-
const omittedItems = plannedItems.filter((item) => !includedIds.has(item.id));
|
|
47
|
-
for (const item of omittedItems) {
|
|
48
|
-
diagnostics.push({ reason: "item_omitted", itemId: item.id, detail: `${item.kind} omitted from context budget` });
|
|
49
|
-
}
|
|
50
|
-
const omittedTokens = omittedItems.reduce((sum, item) => sum + item.estimatedTokens, 0);
|
|
51
|
-
const omittedHighPriority = omittedItems.some((item) => item.priority === "high");
|
|
52
|
-
const emergency = usedTokens > availableTokens || diagnostics.some((diagnostic) => diagnostic.reason === "invalid_budget");
|
|
53
|
-
const policyVersion = input.policyVersion ?? CONTEXT_BUDGET_POLICY_VERSION;
|
|
54
|
-
const planWithoutHash = {
|
|
55
|
-
policyVersion,
|
|
56
|
-
maxTokens,
|
|
57
|
-
responseReserveTokens,
|
|
58
|
-
availableTokens,
|
|
59
|
-
usedTokens,
|
|
60
|
-
omittedTokens,
|
|
61
|
-
emergency,
|
|
62
|
-
omittedHighPriority,
|
|
63
|
-
included: includedItems.map((item) => [item.id, item.estimatedTokens, item.score]),
|
|
64
|
-
omitted: omittedItems.map((item) => [item.id, item.estimatedTokens, item.score]),
|
|
65
|
-
};
|
|
66
|
-
const planHash = createHash("sha256").update(JSON.stringify(planWithoutHash), "utf8").digest("hex");
|
|
67
|
-
const observability = buildObservability({
|
|
68
|
-
availableTokens,
|
|
69
|
-
diagnostics,
|
|
70
|
-
includedItems,
|
|
71
|
-
omittedItems,
|
|
72
|
-
omittedTokens,
|
|
73
|
-
planHash,
|
|
74
|
-
usedTokens,
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
policyVersion,
|
|
78
|
-
maxTokens,
|
|
79
|
-
responseReserveTokens,
|
|
80
|
-
availableTokens,
|
|
81
|
-
usedTokens,
|
|
82
|
-
omittedTokens,
|
|
83
|
-
emergency,
|
|
84
|
-
omittedHighPriority,
|
|
85
|
-
includedItems,
|
|
86
|
-
omittedItems,
|
|
87
|
-
diagnostics,
|
|
88
|
-
planHash,
|
|
89
|
-
observability,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
export function scoreContextBudgetItem(item, estimatedTokens) {
|
|
93
|
-
const priorityScore = priorityWeight(item.priority);
|
|
94
|
-
const relevance = clampScore(item.relevance) * 20;
|
|
95
|
-
const recency = clampScore(item.recency) * 10;
|
|
96
|
-
const evidence = clampScore(item.evidenceValue) * 15;
|
|
97
|
-
// G1: costPenalty 비례화 — sqrt 기반으로 대항목에 더 큰 불이익 부여.
|
|
98
|
-
// log2(t)*2는 1000토큰에서 ~19.9점으로 relevance(20점)와 동급 → 대항목 생존.
|
|
99
|
-
// sqrt(t)*0.5는 1000토큰 15.8점, 5000토큰 35.4점, 10000토큰 50점 → 비례적 증가.
|
|
100
|
-
const costPenalty = Math.sqrt(normalizeTokenCount(estimatedTokens)) * 0.5;
|
|
101
|
-
return priorityScore + relevance + recency + evidence - costPenalty;
|
|
102
|
-
}
|
|
103
|
-
function planItem(item, tokenCounter, modelId, diagnostics) {
|
|
104
|
-
const rawEstimate = item.tokenEstimate ?? tokenCounter.countText(item.text, modelId).tokens;
|
|
105
|
-
const estimatedTokens = normalizeItemTokenEstimate(item.id, rawEstimate, diagnostics);
|
|
106
|
-
return {
|
|
107
|
-
...item,
|
|
108
|
-
estimatedTokens,
|
|
109
|
-
score: item.priority === "hard" ? Number.POSITIVE_INFINITY : scoreContextBudgetItem(item, estimatedTokens),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
const REDUNDANCY_PENALTY_FLOOR = 10;
|
|
113
|
-
const REDUNDANCY_PENALTY_SCALE = 0.8;
|
|
114
|
-
/**
|
|
115
|
-
* W4: Adaptive redundancy penalty — proportional to token count.
|
|
116
|
-
* Large duplicates waste more budget so deserve higher penalty.
|
|
117
|
-
* Formula: max(floor, sqrt(tokens) * scale)
|
|
118
|
-
* - 200 tokens → 11.3 (near floor — minimal waste)
|
|
119
|
-
* - 1000 tokens → 25.3 (moderate waste)
|
|
120
|
-
* - 5000 tokens → 56.6 (heavy waste — strongly penalized)
|
|
121
|
-
* Floor ensures small duplicates are still penalized meaningfully.
|
|
122
|
-
* Scale (0.8) tuned to score medium=50 so large duplicates go negative.
|
|
123
|
-
*/
|
|
124
|
-
function redundancyPenalty(estimatedTokens) {
|
|
125
|
-
return Math.max(REDUNDANCY_PENALTY_FLOOR, Math.sqrt(estimatedTokens) * REDUNDANCY_PENALTY_SCALE);
|
|
126
|
-
}
|
|
127
|
-
function applyRedundancyPenalty(item, selectedRedundancyKeys) {
|
|
128
|
-
if (!item.redundancyKey || !selectedRedundancyKeys.has(item.redundancyKey)) {
|
|
129
|
-
return item;
|
|
130
|
-
}
|
|
131
|
-
return { ...item, score: item.score - redundancyPenalty(item.estimatedTokens) };
|
|
132
|
-
}
|
|
133
|
-
function comparePlannedItemsForSelection(a, b) {
|
|
134
|
-
// G2: 우선순위 티어 내부에서 밀도(score/tokens) 기반 정렬.
|
|
135
|
-
// 단위 토큰당 가치가 높은 항목을 우선 선택 → 예산 활용률 극대화.
|
|
136
|
-
const aTier = priorityTier(a.priority);
|
|
137
|
-
const bTier = priorityTier(b.priority);
|
|
138
|
-
if (aTier !== bTier) {
|
|
139
|
-
return aTier - bTier;
|
|
140
|
-
}
|
|
141
|
-
const aDensity = a.score > 0 ? a.score / Math.max(a.estimatedTokens, 1) : 0;
|
|
142
|
-
const bDensity = b.score > 0 ? b.score / Math.max(b.estimatedTokens, 1) : 0;
|
|
143
|
-
if (aDensity !== bDensity) {
|
|
144
|
-
return bDensity - aDensity;
|
|
145
|
-
}
|
|
146
|
-
if (a.estimatedTokens !== b.estimatedTokens) {
|
|
147
|
-
return a.estimatedTokens - b.estimatedTokens;
|
|
148
|
-
}
|
|
149
|
-
return a.id.localeCompare(b.id);
|
|
150
|
-
}
|
|
151
|
-
function priorityTier(priority) {
|
|
152
|
-
switch (priority) {
|
|
153
|
-
case "hard":
|
|
154
|
-
return 0;
|
|
155
|
-
case "high":
|
|
156
|
-
return 1;
|
|
157
|
-
case "medium":
|
|
158
|
-
return 2;
|
|
159
|
-
case "low":
|
|
160
|
-
return 3;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function priorityWeight(priority) {
|
|
164
|
-
switch (priority) {
|
|
165
|
-
case "hard":
|
|
166
|
-
return 1_000_000;
|
|
167
|
-
case "high":
|
|
168
|
-
return 100;
|
|
169
|
-
case "medium":
|
|
170
|
-
return 50;
|
|
171
|
-
case "low":
|
|
172
|
-
return 10;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
function clampScore(value) {
|
|
176
|
-
if (value === undefined || !Number.isFinite(value)) {
|
|
177
|
-
return 0;
|
|
178
|
-
}
|
|
179
|
-
return Math.max(0, Math.min(1, value));
|
|
180
|
-
}
|
|
181
|
-
function normalizeBudgetTokens(field, value, diagnostics) {
|
|
182
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
183
|
-
diagnostics.push({
|
|
184
|
-
reason: "invalid_budget",
|
|
185
|
-
detail: `${field} must be a non-negative finite number, got ${value}`,
|
|
186
|
-
});
|
|
187
|
-
return 0;
|
|
188
|
-
}
|
|
189
|
-
return Math.floor(value);
|
|
190
|
-
}
|
|
191
|
-
function normalizeItemTokenEstimate(itemId, value, diagnostics) {
|
|
192
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
193
|
-
diagnostics.push({
|
|
194
|
-
reason: "invalid_budget",
|
|
195
|
-
itemId,
|
|
196
|
-
detail: `tokenEstimate must be a non-negative finite number, got ${value}`,
|
|
197
|
-
});
|
|
198
|
-
return 0;
|
|
199
|
-
}
|
|
200
|
-
return Math.ceil(value);
|
|
201
|
-
}
|
|
202
|
-
function normalizeTokenCount(value) {
|
|
203
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
204
|
-
return 0;
|
|
205
|
-
}
|
|
206
|
-
return value;
|
|
207
|
-
}
|
|
208
|
-
function buildObservability(input) {
|
|
209
|
-
return {
|
|
210
|
-
counts: {
|
|
211
|
-
selected: input.includedItems.length,
|
|
212
|
-
omitted: input.omittedItems.length,
|
|
213
|
-
},
|
|
214
|
-
diagnosticReasons: [...new Set(input.diagnostics.map((diagnostic) => diagnostic.reason))].sort(),
|
|
215
|
-
tokens: {
|
|
216
|
-
available: input.availableTokens,
|
|
217
|
-
used: input.usedTokens,
|
|
218
|
-
omitted: input.omittedTokens,
|
|
219
|
-
tokenSavings: input.omittedTokens,
|
|
220
|
-
},
|
|
221
|
-
planHash: input.planHash,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
//# sourceMappingURL=context-budget-governor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-governor.js","sourceRoot":"","sources":["../../src/core/context-budget-governor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { createFallbackTokenCounter, type TokenCounterAdapter } from \"./context-budget-token-counter.ts\";\n\nexport type ContextBudgetItemKind =\n\t| \"system\"\n\t| \"context-pointer\"\n\t| \"context-full\"\n\t| \"skill-header\"\n\t| \"skill\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"tool-result\"\n\t| \"subagent-evidence\"\n\t| \"budget-note\";\n\nexport type ContextBudgetPriority = \"hard\" | \"high\" | \"medium\" | \"low\";\nexport type ContextBudgetDiagnosticReason = \"hard_pin_over_capacity\" | \"item_omitted\" | \"invalid_budget\";\n\nexport interface ContextBudgetItem {\n\treadonly id: string;\n\treadonly kind: ContextBudgetItemKind;\n\treadonly text: string;\n\treadonly priority: ContextBudgetPriority;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly redundancyKey?: string;\n}\n\nexport interface ContextBudgetGovernorInput {\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens?: number;\n\treadonly modelId?: string;\n\treadonly policyVersion?: string;\n\treadonly items: readonly ContextBudgetItem[];\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nexport interface ContextBudgetPlannedItem extends ContextBudgetItem {\n\treadonly estimatedTokens: number;\n\treadonly score: number;\n}\n\nexport interface ContextBudgetDiagnostic {\n\treadonly reason: ContextBudgetDiagnosticReason;\n\treadonly itemId?: string;\n\treadonly detail: string;\n}\n\nexport interface ContextBudgetObservabilityCounts {\n\treadonly selected: number;\n\treadonly omitted: number;\n}\n\nexport interface ContextBudgetObservabilityTokens {\n\treadonly available: number;\n\treadonly used: number;\n\treadonly omitted: number;\n\treadonly tokenSavings: number;\n}\n\nexport interface ContextBudgetObservability {\n\treadonly counts: ContextBudgetObservabilityCounts;\n\treadonly diagnosticReasons: readonly ContextBudgetDiagnosticReason[];\n\treadonly tokens: ContextBudgetObservabilityTokens;\n\treadonly planHash: string;\n}\n\nexport interface ContextBudgetPlan {\n\treadonly policyVersion: string;\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens: number;\n\treadonly availableTokens: number;\n\treadonly usedTokens: number;\n\treadonly omittedTokens: number;\n\treadonly emergency: boolean;\n\treadonly omittedHighPriority: boolean;\n\treadonly includedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedItems: readonly ContextBudgetPlannedItem[];\n\treadonly diagnostics: readonly ContextBudgetDiagnostic[];\n\treadonly planHash: string;\n\treadonly observability: ContextBudgetObservability;\n}\n\nexport const CONTEXT_BUDGET_POLICY_VERSION = \"context-budget-v1\";\n\nexport function planContextBudget(input: ContextBudgetGovernorInput): ContextBudgetPlan {\n\tconst tokenCounter = input.tokenCounter ?? createFallbackTokenCounter();\n\tconst modelId = input.modelId ?? \"unknown\";\n\tconst diagnostics: ContextBudgetDiagnostic[] = [];\n\tconst maxTokens = normalizeBudgetTokens(\"maxTokens\", input.maxTokens, diagnostics);\n\tconst responseReserveTokens = normalizeBudgetTokens(\n\t\t\"responseReserveTokens\",\n\t\tinput.responseReserveTokens ?? 0,\n\t\tdiagnostics,\n\t);\n\tconst availableTokens = Math.max(0, maxTokens - responseReserveTokens);\n\tconst plannedItems = input.items.map((item) => planItem(item, tokenCounter, modelId, diagnostics));\n\tconst includedIds = new Set<string>();\n\tlet usedTokens = 0;\n\n\tfor (const item of plannedItems) {\n\t\tif (item.priority !== \"hard\") {\n\t\t\tcontinue;\n\t\t}\n\t\tincludedIds.add(item.id);\n\t\tusedTokens += item.estimatedTokens;\n\t}\n\n\tif (usedTokens > availableTokens) {\n\t\tdiagnostics.push({\n\t\t\treason: \"hard_pin_over_capacity\",\n\t\t\tdetail: `hard-pinned context uses ${usedTokens} tokens against ${availableTokens} available tokens`,\n\t\t});\n\t}\n\n\tconst selectedRedundancyKeys = new Set<string>();\n\tconst remainingOptionalItems = plannedItems.filter((item) => item.priority !== \"hard\");\n\n\twhile (remainingOptionalItems.length > 0) {\n\t\tconst rankedItems = remainingOptionalItems\n\t\t\t.map((item) => applyRedundancyPenalty(item, selectedRedundancyKeys))\n\t\t\t.sort(comparePlannedItemsForSelection);\n\t\tconst selected = rankedItems.find((item) => usedTokens + item.estimatedTokens <= availableTokens);\n\t\tif (!selected) {\n\t\t\tbreak;\n\t\t}\n\n\t\tincludedIds.add(selected.id);\n\t\tusedTokens += selected.estimatedTokens;\n\t\tif (selected.redundancyKey) {\n\t\t\tselectedRedundancyKeys.add(selected.redundancyKey);\n\t\t}\n\n\t\tconst selectedIndex = remainingOptionalItems.findIndex((item) => item.id === selected.id);\n\t\tremainingOptionalItems.splice(selectedIndex, 1);\n\t}\n\n\tconst includedItems = plannedItems.filter((item) => includedIds.has(item.id));\n\tconst omittedItems = plannedItems.filter((item) => !includedIds.has(item.id));\n\tfor (const item of omittedItems) {\n\t\tdiagnostics.push({ reason: \"item_omitted\", itemId: item.id, detail: `${item.kind} omitted from context budget` });\n\t}\n\tconst omittedTokens = omittedItems.reduce((sum, item) => sum + item.estimatedTokens, 0);\n\tconst omittedHighPriority = omittedItems.some((item) => item.priority === \"high\");\n\tconst emergency =\n\t\tusedTokens > availableTokens || diagnostics.some((diagnostic) => diagnostic.reason === \"invalid_budget\");\n\tconst policyVersion = input.policyVersion ?? CONTEXT_BUDGET_POLICY_VERSION;\n\n\tconst planWithoutHash = {\n\t\tpolicyVersion,\n\t\tmaxTokens,\n\t\tresponseReserveTokens,\n\t\tavailableTokens,\n\t\tusedTokens,\n\t\tomittedTokens,\n\t\temergency,\n\t\tomittedHighPriority,\n\t\tincluded: includedItems.map((item) => [item.id, item.estimatedTokens, item.score]),\n\t\tomitted: omittedItems.map((item) => [item.id, item.estimatedTokens, item.score]),\n\t};\n\tconst planHash = createHash(\"sha256\").update(JSON.stringify(planWithoutHash), \"utf8\").digest(\"hex\");\n\tconst observability = buildObservability({\n\t\tavailableTokens,\n\t\tdiagnostics,\n\t\tincludedItems,\n\t\tomittedItems,\n\t\tomittedTokens,\n\t\tplanHash,\n\t\tusedTokens,\n\t});\n\n\treturn {\n\t\tpolicyVersion,\n\t\tmaxTokens,\n\t\tresponseReserveTokens,\n\t\tavailableTokens,\n\t\tusedTokens,\n\t\tomittedTokens,\n\t\temergency,\n\t\tomittedHighPriority,\n\t\tincludedItems,\n\t\tomittedItems,\n\t\tdiagnostics,\n\t\tplanHash,\n\t\tobservability,\n\t};\n}\n\nexport function scoreContextBudgetItem(item: ContextBudgetItem, estimatedTokens: number): number {\n\tconst priorityScore = priorityWeight(item.priority);\n\tconst relevance = clampScore(item.relevance) * 20;\n\tconst recency = clampScore(item.recency) * 10;\n\tconst evidence = clampScore(item.evidenceValue) * 15;\n\t// G1: costPenalty 비례화 — sqrt 기반으로 대항목에 더 큰 불이익 부여.\n\t// log2(t)*2는 1000토큰에서 ~19.9점으로 relevance(20점)와 동급 → 대항목 생존.\n\t// sqrt(t)*0.5는 1000토큰 15.8점, 5000토큰 35.4점, 10000토큰 50점 → 비례적 증가.\n\tconst costPenalty = Math.sqrt(normalizeTokenCount(estimatedTokens)) * 0.5;\n\treturn priorityScore + relevance + recency + evidence - costPenalty;\n}\n\nfunction planItem(\n\titem: ContextBudgetItem,\n\ttokenCounter: TokenCounterAdapter,\n\tmodelId: string,\n\tdiagnostics: ContextBudgetDiagnostic[],\n): ContextBudgetPlannedItem {\n\tconst rawEstimate = item.tokenEstimate ?? tokenCounter.countText(item.text, modelId).tokens;\n\tconst estimatedTokens = normalizeItemTokenEstimate(item.id, rawEstimate, diagnostics);\n\treturn {\n\t\t...item,\n\t\testimatedTokens,\n\t\tscore: item.priority === \"hard\" ? Number.POSITIVE_INFINITY : scoreContextBudgetItem(item, estimatedTokens),\n\t};\n}\n\nconst REDUNDANCY_PENALTY_FLOOR = 10;\nconst REDUNDANCY_PENALTY_SCALE = 0.8;\n\n/**\n * W4: Adaptive redundancy penalty — proportional to token count.\n * Large duplicates waste more budget so deserve higher penalty.\n * Formula: max(floor, sqrt(tokens) * scale)\n * - 200 tokens → 11.3 (near floor — minimal waste)\n * - 1000 tokens → 25.3 (moderate waste)\n * - 5000 tokens → 56.6 (heavy waste — strongly penalized)\n * Floor ensures small duplicates are still penalized meaningfully.\n * Scale (0.8) tuned to score medium=50 so large duplicates go negative.\n */\nfunction redundancyPenalty(estimatedTokens: number): number {\n\treturn Math.max(REDUNDANCY_PENALTY_FLOOR, Math.sqrt(estimatedTokens) * REDUNDANCY_PENALTY_SCALE);\n}\n\nfunction applyRedundancyPenalty(\n\titem: ContextBudgetPlannedItem,\n\tselectedRedundancyKeys: Set<string>,\n): ContextBudgetPlannedItem {\n\tif (!item.redundancyKey || !selectedRedundancyKeys.has(item.redundancyKey)) {\n\t\treturn item;\n\t}\n\treturn { ...item, score: item.score - redundancyPenalty(item.estimatedTokens) };\n}\n\nfunction comparePlannedItemsForSelection(a: ContextBudgetPlannedItem, b: ContextBudgetPlannedItem): number {\n\t// G2: 우선순위 티어 내부에서 밀도(score/tokens) 기반 정렬.\n\t// 단위 토큰당 가치가 높은 항목을 우선 선택 → 예산 활용률 극대화.\n\tconst aTier = priorityTier(a.priority);\n\tconst bTier = priorityTier(b.priority);\n\tif (aTier !== bTier) {\n\t\treturn aTier - bTier;\n\t}\n\tconst aDensity = a.score > 0 ? a.score / Math.max(a.estimatedTokens, 1) : 0;\n\tconst bDensity = b.score > 0 ? b.score / Math.max(b.estimatedTokens, 1) : 0;\n\tif (aDensity !== bDensity) {\n\t\treturn bDensity - aDensity;\n\t}\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.id.localeCompare(b.id);\n}\n\nfunction priorityTier(priority: ContextBudgetPriority): number {\n\tswitch (priority) {\n\t\tcase \"hard\":\n\t\t\treturn 0;\n\t\tcase \"high\":\n\t\t\treturn 1;\n\t\tcase \"medium\":\n\t\t\treturn 2;\n\t\tcase \"low\":\n\t\t\treturn 3;\n\t}\n}\n\nfunction priorityWeight(priority: ContextBudgetPriority): number {\n\tswitch (priority) {\n\t\tcase \"hard\":\n\t\t\treturn 1_000_000;\n\t\tcase \"high\":\n\t\t\treturn 100;\n\t\tcase \"medium\":\n\t\t\treturn 50;\n\t\tcase \"low\":\n\t\t\treturn 10;\n\t}\n}\n\nfunction clampScore(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\treturn Math.max(0, Math.min(1, value));\n}\n\nfunction normalizeBudgetTokens(\n\tfield: \"maxTokens\" | \"responseReserveTokens\",\n\tvalue: number,\n\tdiagnostics: ContextBudgetDiagnostic[],\n): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tdiagnostics.push({\n\t\t\treason: \"invalid_budget\",\n\t\t\tdetail: `${field} must be a non-negative finite number, got ${value}`,\n\t\t});\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction normalizeItemTokenEstimate(itemId: string, value: number, diagnostics: ContextBudgetDiagnostic[]): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tdiagnostics.push({\n\t\t\treason: \"invalid_budget\",\n\t\t\titemId,\n\t\t\tdetail: `tokenEstimate must be a non-negative finite number, got ${value}`,\n\t\t});\n\t\treturn 0;\n\t}\n\treturn Math.ceil(value);\n}\n\nfunction normalizeTokenCount(value: number): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\treturn 0;\n\t}\n\treturn value;\n}\n\nfunction buildObservability(input: {\n\treadonly availableTokens: number;\n\treadonly diagnostics: readonly ContextBudgetDiagnostic[];\n\treadonly includedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedItems: readonly ContextBudgetPlannedItem[];\n\treadonly omittedTokens: number;\n\treadonly planHash: string;\n\treadonly usedTokens: number;\n}): ContextBudgetObservability {\n\treturn {\n\t\tcounts: {\n\t\t\tselected: input.includedItems.length,\n\t\t\tomitted: input.omittedItems.length,\n\t\t},\n\t\tdiagnosticReasons: [...new Set(input.diagnostics.map((diagnostic) => diagnostic.reason))].sort(),\n\t\ttokens: {\n\t\t\tavailable: input.availableTokens,\n\t\t\tused: input.usedTokens,\n\t\t\tomitted: input.omittedTokens,\n\t\t\ttokenSavings: input.omittedTokens,\n\t\t},\n\t\tplanHash: input.planHash,\n\t};\n}\n"]}
|
package/dist/core/lean-ctx.d.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
declare const DEFAULT_CONFIG: {
|
|
3
|
-
recencyWindow: number;
|
|
4
|
-
relevanceThreshold: number;
|
|
5
|
-
keyTurnReserve: number;
|
|
6
|
-
summaryEvery: number;
|
|
7
|
-
maxContextTokens: number;
|
|
8
|
-
compressionTarget: number;
|
|
9
|
-
tfidfWeight: number;
|
|
10
|
-
semanticWeight: number;
|
|
11
|
-
recencyWeight: number;
|
|
12
|
-
keywordBoost: number;
|
|
13
|
-
lruWeight: number;
|
|
14
|
-
frequencyWeight: number;
|
|
15
|
-
recencyDecayWeight: number;
|
|
16
|
-
protectedKeywords: string[];
|
|
17
|
-
hardTokenLimit: number;
|
|
18
|
-
softTokenLimit: number;
|
|
19
|
-
warningThreshold: number;
|
|
20
|
-
criticalThreshold: number;
|
|
21
|
-
gracefulDegradation: boolean;
|
|
22
|
-
opportunisticExpansion: boolean;
|
|
23
|
-
autoCompress: boolean;
|
|
24
|
-
compressOnEveryPrompt: boolean;
|
|
25
|
-
minTokensToCompress: number;
|
|
26
|
-
maxTurnsBeforeCompress: number;
|
|
27
|
-
};
|
|
28
|
-
export interface TurnEntry {
|
|
29
|
-
index: number;
|
|
30
|
-
role: "user" | "assistant" | "system" | "tool";
|
|
31
|
-
content: string;
|
|
32
|
-
timestamp: number;
|
|
33
|
-
metadata?: Record<string, any>;
|
|
34
|
-
tokenCount?: number;
|
|
35
|
-
isKeyTurn?: boolean;
|
|
36
|
-
relevanceScore?: number;
|
|
37
|
-
accessCount?: number;
|
|
38
|
-
lastAccessed?: number;
|
|
39
|
-
}
|
|
40
|
-
export interface CompressedContext {
|
|
41
|
-
turns: TurnEntry[];
|
|
42
|
-
summary: string;
|
|
43
|
-
originalTokens: number;
|
|
44
|
-
compressedTokens: number;
|
|
45
|
-
compressionRatio: number;
|
|
46
|
-
techniqueUsed: string;
|
|
47
|
-
keyTurnsPreserved: number;
|
|
48
|
-
evictedCount: number;
|
|
49
|
-
metadata?: Record<string, any>;
|
|
50
|
-
}
|
|
51
|
-
export interface BudgetStatus {
|
|
52
|
-
usedTokens: number;
|
|
53
|
-
totalTokens: number;
|
|
54
|
-
ratio: number;
|
|
55
|
-
level: "green" | "yellow" | "orange" | "red" | "exceeded";
|
|
56
|
-
remaining: number;
|
|
57
|
-
canAddAgents: boolean;
|
|
58
|
-
}
|
|
59
|
-
export declare class ContextCompressor extends EventEmitter {
|
|
60
|
-
private config;
|
|
61
|
-
private charsPerToken;
|
|
62
|
-
constructor(config?: Partial<typeof DEFAULT_CONFIG>);
|
|
63
|
-
compress(context: TurnEntry[], currentTurn?: TurnEntry): CompressedContext;
|
|
64
|
-
private estimateTokens;
|
|
65
|
-
private extractKeyTurns;
|
|
66
|
-
private applyRecencyWindow;
|
|
67
|
-
private filterByRelevance;
|
|
68
|
-
private calculateRelevance;
|
|
69
|
-
private tfidfSimilarity;
|
|
70
|
-
private hierarchicalSummarize;
|
|
71
|
-
private assembleCompressed;
|
|
72
|
-
}
|
|
73
|
-
export declare class TokenBudgetEnforcer extends EventEmitter {
|
|
74
|
-
private config;
|
|
75
|
-
constructor(config?: Partial<typeof DEFAULT_CONFIG>);
|
|
76
|
-
check(usedTokens: number): BudgetStatus;
|
|
77
|
-
autoCompressIfNeeded(context: TurnEntry[]): {
|
|
78
|
-
compressed: TurnEntry[];
|
|
79
|
-
status: BudgetStatus;
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export declare class LeanCTXEngine extends EventEmitter {
|
|
83
|
-
private compressor;
|
|
84
|
-
private budgetEnforcer;
|
|
85
|
-
private history;
|
|
86
|
-
constructor(config?: Partial<typeof DEFAULT_CONFIG>);
|
|
87
|
-
processPrompt(prompt: string, role?: "user" | "assistant" | "system"): CompressedContext;
|
|
88
|
-
preToolCall(_toolName: string, _toolArgs: Record<string, any>, context: TurnEntry[]): {
|
|
89
|
-
compressed: TurnEntry[];
|
|
90
|
-
status: BudgetStatus;
|
|
91
|
-
};
|
|
92
|
-
getStats(): {
|
|
93
|
-
historyLength: number;
|
|
94
|
-
lastCompression: number;
|
|
95
|
-
};
|
|
96
|
-
clearHistory(): void;
|
|
97
|
-
}
|
|
98
|
-
export default LeanCTXEngine;
|
|
99
|
-
//# sourceMappingURL=lean-ctx.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lean-ctx.d.ts","sourceRoot":"","sources":["../../src/core/lean-ctx.ts"],"names":[],"mappings":"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","sourcesContent":["// OMK v6.2 — Lean CTX Engine (TypeScript)\n// =========================================\n// packages/coding-agent/src/core/lean-ctx.ts\n//\n// 매 프롬프트 입력 시 자동으로 context 분석/압축을 수행하는 엔진.\n// 순수 로컬 모드 — API 호출 0회.\n\nimport { EventEmitter } from \"events\";\n\n// ── 상수 및 설정 ────────────────────────────────────────────────────────\n\nconst DEFAULT_CONFIG = {\n\trecencyWindow: 4,\n\trelevanceThreshold: 0.3,\n\tkeyTurnReserve: 2,\n\tsummaryEvery: 3,\n\tmaxContextTokens: 2000,\n\tcompressionTarget: 0.6,\n\n\ttfidfWeight: 0.5,\n\tsemanticWeight: 0.3,\n\trecencyWeight: 0.2,\n\tkeywordBoost: 1.5,\n\n\tlruWeight: 0.4,\n\tfrequencyWeight: 0.3,\n\trecencyDecayWeight: 0.3,\n\tprotectedKeywords: [\"refusal\", \"error\", \"strategy_change\", \"phase_transition\"],\n\n\thardTokenLimit: 15000,\n\tsoftTokenLimit: 12000,\n\twarningThreshold: 0.8,\n\tcriticalThreshold: 0.95,\n\tgracefulDegradation: true,\n\topportunisticExpansion: true,\n\n\tautoCompress: true,\n\tcompressOnEveryPrompt: true,\n\tminTokensToCompress: 500,\n\tmaxTurnsBeforeCompress: 8,\n};\n\n// ── 데이터 모델 ─────────────────────────────────────────────────────────\n\nexport interface TurnEntry {\n\tindex: number;\n\trole: \"user\" | \"assistant\" | \"system\" | \"tool\";\n\tcontent: string;\n\ttimestamp: number;\n\tmetadata?: Record<string, any>;\n\ttokenCount?: number;\n\tisKeyTurn?: boolean;\n\trelevanceScore?: number;\n\taccessCount?: number;\n\tlastAccessed?: number;\n}\n\nexport interface CompressedContext {\n\tturns: TurnEntry[];\n\tsummary: string;\n\toriginalTokens: number;\n\tcompressedTokens: number;\n\tcompressionRatio: number;\n\ttechniqueUsed: string;\n\tkeyTurnsPreserved: number;\n\tevictedCount: number;\n\tmetadata?: Record<string, any>;\n}\n\nexport interface BudgetStatus {\n\tusedTokens: number;\n\ttotalTokens: number;\n\tratio: number;\n\tlevel: \"green\" | \"yellow\" | \"orange\" | \"red\" | \"exceeded\";\n\tremaining: number;\n\tcanAddAgents: boolean;\n}\n\n// ── ContextCompressor ───────────────────────────────────────────────────\n\nexport class ContextCompressor extends EventEmitter {\n\tprivate config: typeof DEFAULT_CONFIG;\n\tprivate charsPerToken = 4;\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\tcompress(context: TurnEntry[], currentTurn?: TurnEntry): CompressedContext {\n\t\tconst originalTokens = this.estimateTokens(context);\n\n\t\tif (originalTokens < this.config.minTokensToCompress) {\n\t\t\treturn {\n\t\t\t\tturns: context,\n\t\t\t\tsummary: \"\",\n\t\t\t\toriginalTokens,\n\t\t\t\tcompressedTokens: originalTokens,\n\t\t\t\tcompressionRatio: 1.0,\n\t\t\t\ttechniqueUsed: \"none\",\n\t\t\t\tkeyTurnsPreserved: 0,\n\t\t\t\tevictedCount: 0,\n\t\t\t};\n\t\t}\n\n\t\tconst keyTurns = this.extractKeyTurns(context);\n\t\tconst recent = this.applyRecencyWindow(context);\n\t\tconst relevant = this.filterByRelevance(context, currentTurn, keyTurns);\n\t\tconst summary = this.hierarchicalSummarize(context, recent, keyTurns, relevant);\n\t\tconst compressed = this.assembleCompressed(keyTurns, relevant, recent, summary);\n\n\t\tconst compressedTokens = this.estimateTokens(compressed);\n\t\tconst ratio = originalTokens > 0 ? compressedTokens / originalTokens : 1.0;\n\n\t\treturn {\n\t\t\tturns: compressed,\n\t\t\tsummary,\n\t\t\toriginalTokens,\n\t\t\tcompressedTokens,\n\t\t\tcompressionRatio: ratio,\n\t\t\ttechniqueUsed: \"hierarchical+selective\",\n\t\t\tkeyTurnsPreserved: keyTurns.length,\n\t\t\tevictedCount: context.length - compressed.length,\n\t\t};\n\t}\n\n\tprivate estimateTokens(turns: TurnEntry[]): number {\n\t\treturn turns.reduce((sum, t) => sum + Math.ceil(t.content.length / this.charsPerToken), 0);\n\t}\n\n\tprivate extractKeyTurns(context: TurnEntry[]): TurnEntry[] {\n\t\tconst keyTurns: TurnEntry[] = [];\n\t\tconst protectedKeywords = this.config.protectedKeywords;\n\n\t\tfor (const turn of context) {\n\t\t\tconst contentLower = turn.content.toLowerCase();\n\t\t\tlet isKey = protectedKeywords.some((kw) => contentLower.includes(kw.toLowerCase()));\n\n\t\t\tif (turn.metadata?.isKeyTurn || turn.metadata?.phaseTransition) {\n\t\t\t\tisKey = true;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tturn.role === \"assistant\" &&\n\t\t\t\t[\"refuse\", \"cannot\", \"unable\", \"sorry\", \"거부\", \"죄송\"].some((w) => contentLower.includes(w))\n\t\t\t) {\n\t\t\t\tisKey = true;\n\t\t\t}\n\n\t\t\tif (isKey) {\n\t\t\t\tturn.isKeyTurn = true;\n\t\t\t\tkeyTurns.push(turn);\n\t\t\t}\n\t\t}\n\n\t\treturn keyTurns;\n\t}\n\n\tprivate applyRecencyWindow(context: TurnEntry[]): TurnEntry[] {\n\t\tconst windowSize = this.config.recencyWindow;\n\t\tif (context.length <= windowSize) return [...context];\n\t\treturn context.slice(-windowSize);\n\t}\n\n\tprivate filterByRelevance(context: TurnEntry[], currentTurn?: TurnEntry, keyTurns?: TurnEntry[]): TurnEntry[] {\n\t\tif (!currentTurn) return [...context];\n\n\t\tconst threshold = this.config.relevanceThreshold;\n\t\tconst keyIndices = new Set(keyTurns?.map((t) => t.index) ?? []);\n\n\t\treturn context.filter((turn) => {\n\t\t\tif (keyIndices.has(turn.index)) return true;\n\t\t\tconst score = this.calculateRelevance(turn, currentTurn);\n\t\t\treturn score >= threshold;\n\t\t});\n\t}\n\n\tprivate calculateRelevance(turn: TurnEntry, currentTurn: TurnEntry): number {\n\t\tconst tfidfScore = this.tfidfSimilarity(turn.content, currentTurn.content);\n\t\tconst recencyScore = Math.exp(-(Date.now() - turn.timestamp) / 3600000);\n\t\treturn tfidfScore * this.config.tfidfWeight + recencyScore * this.config.recencyWeight;\n\t}\n\n\tprivate tfidfSimilarity(text1: string, text2: string): number {\n\t\tconst words1 = text1.toLowerCase().split(/\\s+/);\n\t\tconst words2 = text2.toLowerCase().split(/\\s+/);\n\t\tconst set1 = new Set(words1);\n\t\tconst set2 = new Set(words2);\n\t\tconst intersection = new Set([...set1].filter((x) => set2.has(x)));\n\t\treturn intersection.size / Math.sqrt(set1.size * set2.size);\n\t}\n\n\tprivate hierarchicalSummarize(\n\t\t_context: TurnEntry[],\n\t\trecent: TurnEntry[],\n\t\tkeyTurns: TurnEntry[],\n\t\trelevant: TurnEntry[],\n\t): string {\n\t\tconst allImportant = [...new Set([...keyTurns, ...relevant, ...recent])].sort((a, b) => a.index - b.index);\n\t\tconst chunks: string[] = [];\n\n\t\tfor (let i = 0; i < allImportant.length; i += this.config.summaryEvery) {\n\t\t\tconst chunk = allImportant.slice(i, i + this.config.summaryEvery);\n\t\t\tconst summary = chunk.map((t) => `[${t.role}]: ${t.content.substring(0, 100)}`).join(\" | \");\n\t\t\tchunks.push(summary);\n\t\t}\n\n\t\treturn chunks.join(\"\\n\");\n\t}\n\n\tprivate assembleCompressed(\n\t\tkeyTurns: TurnEntry[],\n\t\trelevant: TurnEntry[],\n\t\trecent: TurnEntry[],\n\t\t_summary: string,\n\t): TurnEntry[] {\n\t\tconst important = new Map<number, TurnEntry>();\n\n\t\tfor (const turn of keyTurns) important.set(turn.index, turn);\n\t\tfor (const turn of relevant) important.set(turn.index, turn);\n\t\tfor (const turn of recent) important.set(turn.index, turn);\n\n\t\treturn Array.from(important.values()).sort((a, b) => a.index - b.index);\n\t}\n}\n\n// ── TokenBudgetEnforcer ─────────────────────────────────────────────────\n\nexport class TokenBudgetEnforcer extends EventEmitter {\n\tprivate config: typeof DEFAULT_CONFIG;\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\tcheck(usedTokens: number): BudgetStatus {\n\t\tconst total = this.config.hardTokenLimit;\n\t\tconst ratio = usedTokens / total;\n\t\tconst remaining = total - usedTokens;\n\n\t\tlet level: BudgetStatus[\"level\"] = \"green\";\n\t\tif (ratio >= 1.0) level = \"exceeded\";\n\t\telse if (ratio >= this.config.criticalThreshold) level = \"red\";\n\t\telse if (ratio >= this.config.warningThreshold) level = \"orange\";\n\t\telse if (ratio >= 0.6) level = \"yellow\";\n\n\t\treturn {\n\t\t\tusedTokens,\n\t\t\ttotalTokens: total,\n\t\t\tratio,\n\t\t\tlevel,\n\t\t\tremaining,\n\t\t\tcanAddAgents: level === \"green\" || level === \"yellow\",\n\t\t};\n\t}\n\n\tautoCompressIfNeeded(context: TurnEntry[]): { compressed: TurnEntry[]; status: BudgetStatus } {\n\t\tconst tokens = context.reduce((sum, t) => sum + Math.ceil(t.content.length / 4), 0);\n\t\tconst status = this.check(tokens);\n\n\t\tif (status.level === \"red\" || status.level === \"exceeded\") {\n\t\t\tconst compressor = new ContextCompressor(this.config);\n\t\t\tconst result = compressor.compress(context);\n\t\t\treturn { compressed: result.turns, status: this.check(result.compressedTokens) };\n\t\t}\n\n\t\treturn { compressed: context, status };\n\t}\n}\n\n// ── LeanCTXEngine (통합 엔진) ───────────────────────────────────────────\n\nexport class LeanCTXEngine extends EventEmitter {\n\tprivate compressor: ContextCompressor;\n\tprivate budgetEnforcer: TokenBudgetEnforcer;\n\tprivate history: TurnEntry[] = [];\n\n\tconstructor(config?: Partial<typeof DEFAULT_CONFIG>) {\n\t\tsuper();\n\t\tthis.compressor = new ContextCompressor(config);\n\t\tthis.budgetEnforcer = new TokenBudgetEnforcer(config);\n\t}\n\n\tprocessPrompt(prompt: string, role: \"user\" | \"assistant\" | \"system\" = \"user\"): CompressedContext {\n\t\tconst currentTurn: TurnEntry = {\n\t\t\tindex: this.history.length,\n\t\t\trole,\n\t\t\tcontent: prompt,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\tconst result = this.compressor.compress(this.history, currentTurn);\n\t\tthis.budgetEnforcer.autoCompressIfNeeded(result.turns);\n\n\t\tthis.history.push(currentTurn);\n\t\tif (this.history.length > 100) this.history = this.history.slice(-50);\n\n\t\treturn result;\n\t}\n\n\tpreToolCall(\n\t\t_toolName: string,\n\t\t_toolArgs: Record<string, any>,\n\t\tcontext: TurnEntry[],\n\t): { compressed: TurnEntry[]; status: BudgetStatus } {\n\t\treturn this.budgetEnforcer.autoCompressIfNeeded(context);\n\t}\n\n\tgetStats(): { historyLength: number; lastCompression: number } {\n\t\treturn { historyLength: this.history.length, lastCompression: Date.now() };\n\t}\n\n\tclearHistory(): void {\n\t\tthis.history = [];\n\t}\n}\n\nexport default LeanCTXEngine;\n"]}
|