open-multi-agent-kit 0.95.0 → 0.95.1
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 +16 -0
- package/README.md +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +39 -5
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/headroom.d.ts.map +1 -1
- package/dist/core/headroom.js +7 -2
- package/dist/core/headroom.js.map +1 -1
- 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/subagent/agents/planner.md +1 -1
- package/examples/extensions/subagent/agents/reviewer.md +1 -1
- package/examples/extensions/subagent/agents/scout.md +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 +15 -15
- package/package.json +5 -5
- package/dist/core/context-budget-compressors.d.ts +0 -50
- package/dist/core/context-budget-compressors.d.ts.map +0 -1
- package/dist/core/context-budget-compressors.js +0 -166
- package/dist/core/context-budget-compressors.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.95.1] - 2026-08-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Harness Graph control plane** (`.omk/harness-graph/`): deterministic agents×skills×hooks×MCP inventory with 3-tier skill classification, bipartite SPOF criticality, Louvain communities, association-rule lift, hybrid CF wiring recommendations (`jaccard · idf · lift_boost`), fail-closed `health_gate.py` + debt allowlist, executive `dashboard.md`, review-only `wiring-patch`, synthetic unit + property tests, and CI workflow `.github/workflows/harness-graph.yml`.
|
|
8
|
+
- **Harness Graph ops tooling**: `compact-skills-index.mjs` (demand-union index rebuild), `prune-retired-hooks.mjs` (retired hook capability cleanup), `apply-wiring-patch.py` (half-bundle completion checklist), session-start drift audit hook with optional `HARNESS_GRAPH_STRICT=1`.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Harness Graph green-metric traps: runtime-derived hook/MCP catalogs (no hardcoded answer keys), bipartite SPOF instead of empty articulation tables, default-only model-drift axis (failover is advisory), skills-index no longer dumps the full on-disk universe into false orphan-active counts.
|
|
13
|
+
|
|
14
|
+
### Docs
|
|
15
|
+
|
|
16
|
+
- Root README: AEO/SEO-oriented positioning, FAQ, and marketing/growth skill keyword map (`omk-marketing` + marketingskills pack).
|
|
17
|
+
- Spec/plan/scorecard for harness-graph engineering (`specs/012-harness-graph-engineering/`, `.omk/harness-graph/SCORECARD.md`).
|
|
18
|
+
|
|
3
19
|
## [0.95.0] - 2026-07-31
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -170,7 +170,7 @@ npm test
|
|
|
170
170
|
|
|
171
171
|
See [Development](https://github.com/dmae97/omk/blob/main/packages/coding-agent/docs/development.md) and [CONTRIBUTING.md](https://github.com/dmae97/omk/blob/main/CONTRIBUTING.md).
|
|
172
172
|
|
|
173
|
-
Release notes: [v0.95.
|
|
173
|
+
Release notes: [v0.95.1](https://github.com/dmae97/omk/blob/main/.github/RELEASE_NOTES_v0.95.1.md).
|
|
174
174
|
|
|
175
175
|
## License
|
|
176
176
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-v2-plan-cache-keys.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-plan-cache-keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACN,KAAK,2BAA2B,EAGhC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"context-budget-v2-plan-cache-keys.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-plan-cache-keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EACN,KAAK,2BAA2B,EAGhC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAkCvE,wBAAgB,gCAAgC,CAAC,KAAK,EAAE;IACvD,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;CAChD,GAAG,MAAM,CA8CT","sourcesContent":["import type { HeadroomQualityPolicyV2 } from \"./context-budget-headroom.ts\";\nimport { sha256Canonical } from \"./context-budget-v2-cache-hash.ts\";\nimport {\n\ttype ContextBudgetCacheKeyBaseV2,\n\tcomputeContextBudgetRepresentationFingerprintV2,\n\tfingerprintSourceRef,\n} from \"./context-budget-v2-cache-keys.ts\";\nimport type { PlannedItemV2 } from \"./context-budget-v2-scoring.ts\";\nimport type { TierBudgetPolicyV2 } from \"./context-budget-v2-types.ts\";\n\n/**\n * Cache-key-only quantization for signals that legitimately drift every turn.\n *\n * `ageTurns`, `baseScore`, `effectiveScore`, `redundancyPenalty`, and an explicit `recency`\n * override are recomputed from scratch on every planning pass (recency decays continuously via\n * `deriveRecency()`'s `exp(-ageTurns / halfLife)`), so hashing their raw values means the plan\n * cache key changes on *every single turn* even when the resulting selection would be identical\n * -- the cache can structurally never hit past the first call in a session. Bucketing/rounding\n * them here does not touch actual selection logic (compareOptionalForSelection, tier allocation,\n * etc. still read the full-precision fields off `planned` directly); it only changes what counts\n * as \"the same plan\" for cache-lookup purposes. `ageTurns` buckets are log2-spaced so a bucket\n * boundary corresponds to roughly one additional recency half-life having elapsed -- the point\n * at which the decayed score has plausibly moved enough to change ranking/selection -- rather\n * than an arbitrary fixed window.\n */\nfunction bucketAgeTurnsForCacheKeyV2(ageTurns: number | null | undefined): number | null {\n\tif (ageTurns === null || ageTurns === undefined || !Number.isFinite(ageTurns)) return null;\n\tif (ageTurns <= 0) return 0;\n\treturn Math.floor(Math.log2(ageTurns + 1));\n}\n\nfunction roundScoreForCacheKeyV2(score: number | null | undefined): number | null {\n\tif (score === null || score === undefined || Number.isNaN(score)) return null;\n\tif (!Number.isFinite(score)) return score; // preserve +/-Infinity (hard-pinned items)\n\treturn Math.round(score);\n}\n\nfunction roundUnitIntervalForCacheKeyV2(value: number | null | undefined): number | null {\n\tif (value === null || value === undefined || !Number.isFinite(value)) return null;\n\treturn Math.round(value * 10) / 10;\n}\n\nexport function buildContextBudgetPlanCacheKeyV2(input: {\n\treadonly keyBase: ContextBudgetCacheKeyBaseV2;\n\treadonly promptHash?: string;\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens: number;\n\treadonly safetyMarginTokens: number;\n\treadonly availableTokens: number;\n\treadonly planned: readonly PlannedItemV2[];\n\treadonly tierPolicy: Readonly<Record<string, TierBudgetPolicyV2>>;\n\treadonly qualityPolicy: HeadroomQualityPolicyV2;\n}): string {\n\treturn `context-plan:${sha256Canonical({\n\t\tavailableTokens: input.availableTokens,\n\t\tbudgetBucket: input.keyBase.budgetBucket,\n\t\titems: input.planned\n\t\t\t.map((planned) => ({\n\t\t\t\tageTurns: bucketAgeTurnsForCacheKeyV2(planned.item.ageTurns),\n\t\t\t\tbaseScore: roundScoreForCacheKeyV2(planned.baseScore),\n\t\t\t\teffectiveScore: roundScoreForCacheKeyV2(planned.effectiveScore),\n\t\t\t\tevidenceKind: planned.item.evidenceKind ?? null,\n\t\t\t\tevidenceValue: planned.item.evidenceValue ?? null,\n\t\t\t\tid: planned.item.id,\n\t\t\t\tpinReason: planned.item.pinReason ?? null,\n\t\t\t\tpriority: planned.item.priority,\n\t\t\t\trecency: roundUnitIntervalForCacheKeyV2(planned.item.recency),\n\t\t\t\tredundancyKey: planned.item.redundancyKey ?? null,\n\t\t\t\tredundancyPenalty: roundScoreForCacheKeyV2(planned.redundancyPenalty),\n\t\t\t\trelevance: planned.item.relevance ?? null,\n\t\t\t\trepresentations: (planned.item.representations ?? [])\n\t\t\t\t\t.map((candidate) => ({\n\t\t\t\t\t\tfingerprint: computeContextBudgetRepresentationFingerprintV2(candidate),\n\t\t\t\t\t\tkind: candidate.kind,\n\t\t\t\t\t}))\n\t\t\t\t\t.sort((a, b) => a.kind.localeCompare(b.kind) || a.fingerprint.localeCompare(b.fingerprint)),\n\t\t\t\trequired: planned.item.required === true,\n\t\t\t\tsourceRef: fingerprintSourceRef(planned.item.sourceRef),\n\t\t\t\tsourceHash: planned.contentHash,\n\t\t\t\ttier: planned.item.tier,\n\t\t\t\ttokenEstimate: planned.item.tokenEstimate ?? null,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.id.localeCompare(b.id)),\n\t\tmaxTokens: input.maxTokens,\n\t\tinvalidationSnapshotHash: input.keyBase.invalidationSnapshotHash ?? \"none\",\n\t\tmodelId: input.keyBase.modelId,\n\t\tnamespace: input.keyBase.namespace,\n\t\tpolicyVersion: input.keyBase.policyVersion,\n\t\tpromptHash: input.promptHash ?? null,\n\t\tqueryIntentHash: input.keyBase.queryIntentHash,\n\t\tredactionPolicyHash: input.keyBase.redactionPolicyHash,\n\t\tresponseReserveTokens: input.responseReserveTokens,\n\t\tsafetyMarginTokens: input.safetyMarginTokens,\n\t\tsafetyProfileHash: input.keyBase.safetyProfileHash,\n\t\ttierPolicy: input.tierPolicy,\n\t\tqualityPolicy: input.qualityPolicy,\n\t\ttokenizerId: input.keyBase.tokenizerId,\n\t})}`;\n}\n"]}
|
|
@@ -1,22 +1,56 @@
|
|
|
1
1
|
import { sha256Canonical } from "./context-budget-v2-cache-hash.js";
|
|
2
2
|
import { computeContextBudgetRepresentationFingerprintV2, fingerprintSourceRef, } from "./context-budget-v2-cache-keys.js";
|
|
3
|
+
/**
|
|
4
|
+
* Cache-key-only quantization for signals that legitimately drift every turn.
|
|
5
|
+
*
|
|
6
|
+
* `ageTurns`, `baseScore`, `effectiveScore`, `redundancyPenalty`, and an explicit `recency`
|
|
7
|
+
* override are recomputed from scratch on every planning pass (recency decays continuously via
|
|
8
|
+
* `deriveRecency()`'s `exp(-ageTurns / halfLife)`), so hashing their raw values means the plan
|
|
9
|
+
* cache key changes on *every single turn* even when the resulting selection would be identical
|
|
10
|
+
* -- the cache can structurally never hit past the first call in a session. Bucketing/rounding
|
|
11
|
+
* them here does not touch actual selection logic (compareOptionalForSelection, tier allocation,
|
|
12
|
+
* etc. still read the full-precision fields off `planned` directly); it only changes what counts
|
|
13
|
+
* as "the same plan" for cache-lookup purposes. `ageTurns` buckets are log2-spaced so a bucket
|
|
14
|
+
* boundary corresponds to roughly one additional recency half-life having elapsed -- the point
|
|
15
|
+
* at which the decayed score has plausibly moved enough to change ranking/selection -- rather
|
|
16
|
+
* than an arbitrary fixed window.
|
|
17
|
+
*/
|
|
18
|
+
function bucketAgeTurnsForCacheKeyV2(ageTurns) {
|
|
19
|
+
if (ageTurns === null || ageTurns === undefined || !Number.isFinite(ageTurns))
|
|
20
|
+
return null;
|
|
21
|
+
if (ageTurns <= 0)
|
|
22
|
+
return 0;
|
|
23
|
+
return Math.floor(Math.log2(ageTurns + 1));
|
|
24
|
+
}
|
|
25
|
+
function roundScoreForCacheKeyV2(score) {
|
|
26
|
+
if (score === null || score === undefined || Number.isNaN(score))
|
|
27
|
+
return null;
|
|
28
|
+
if (!Number.isFinite(score))
|
|
29
|
+
return score; // preserve +/-Infinity (hard-pinned items)
|
|
30
|
+
return Math.round(score);
|
|
31
|
+
}
|
|
32
|
+
function roundUnitIntervalForCacheKeyV2(value) {
|
|
33
|
+
if (value === null || value === undefined || !Number.isFinite(value))
|
|
34
|
+
return null;
|
|
35
|
+
return Math.round(value * 10) / 10;
|
|
36
|
+
}
|
|
3
37
|
export function buildContextBudgetPlanCacheKeyV2(input) {
|
|
4
38
|
return `context-plan:${sha256Canonical({
|
|
5
39
|
availableTokens: input.availableTokens,
|
|
6
40
|
budgetBucket: input.keyBase.budgetBucket,
|
|
7
41
|
items: input.planned
|
|
8
42
|
.map((planned) => ({
|
|
9
|
-
ageTurns: planned.item.ageTurns
|
|
10
|
-
baseScore: planned.baseScore,
|
|
11
|
-
effectiveScore: planned.effectiveScore,
|
|
43
|
+
ageTurns: bucketAgeTurnsForCacheKeyV2(planned.item.ageTurns),
|
|
44
|
+
baseScore: roundScoreForCacheKeyV2(planned.baseScore),
|
|
45
|
+
effectiveScore: roundScoreForCacheKeyV2(planned.effectiveScore),
|
|
12
46
|
evidenceKind: planned.item.evidenceKind ?? null,
|
|
13
47
|
evidenceValue: planned.item.evidenceValue ?? null,
|
|
14
48
|
id: planned.item.id,
|
|
15
49
|
pinReason: planned.item.pinReason ?? null,
|
|
16
50
|
priority: planned.item.priority,
|
|
17
|
-
recency: planned.item.recency
|
|
51
|
+
recency: roundUnitIntervalForCacheKeyV2(planned.item.recency),
|
|
18
52
|
redundancyKey: planned.item.redundancyKey ?? null,
|
|
19
|
-
redundancyPenalty: planned.redundancyPenalty,
|
|
53
|
+
redundancyPenalty: roundScoreForCacheKeyV2(planned.redundancyPenalty),
|
|
20
54
|
relevance: planned.item.relevance ?? null,
|
|
21
55
|
representations: (planned.item.representations ?? [])
|
|
22
56
|
.map((candidate) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-v2-plan-cache-keys.js","sourceRoot":"","sources":["../../src/core/context-budget-v2-plan-cache-keys.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAEN,+CAA+C,EAC/C,oBAAoB,GACpB,MAAM,mCAAmC,CAAC;AAI3C,MAAM,UAAU,gCAAgC,CAAC,KAUhD,EAAU;IACV,OAAO,gBAAgB,eAAe,CAAC;QACtC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY;QACxC,KAAK,EAAE,KAAK,CAAC,OAAO;aAClB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAClB,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"context-budget-v2-plan-cache-keys.js","sourceRoot":"","sources":["../../src/core/context-budget-v2-plan-cache-keys.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HeadroomQualityPolicyV2 } from \"./context-budget-headroom.ts\";\nimport { sha256Canonical } from \"./context-budget-v2-cache-hash.ts\";\nimport {\n\ttype ContextBudgetCacheKeyBaseV2,\n\tcomputeContextBudgetRepresentationFingerprintV2,\n\tfingerprintSourceRef,\n} from \"./context-budget-v2-cache-keys.ts\";\nimport type { PlannedItemV2 } from \"./context-budget-v2-scoring.ts\";\nimport type { TierBudgetPolicyV2 } from \"./context-budget-v2-types.ts\";\n\n/**\n * Cache-key-only quantization for signals that legitimately drift every turn.\n *\n * `ageTurns`, `baseScore`, `effectiveScore`, `redundancyPenalty`, and an explicit `recency`\n * override are recomputed from scratch on every planning pass (recency decays continuously via\n * `deriveRecency()`'s `exp(-ageTurns / halfLife)`), so hashing their raw values means the plan\n * cache key changes on *every single turn* even when the resulting selection would be identical\n * -- the cache can structurally never hit past the first call in a session. Bucketing/rounding\n * them here does not touch actual selection logic (compareOptionalForSelection, tier allocation,\n * etc. still read the full-precision fields off `planned` directly); it only changes what counts\n * as \"the same plan\" for cache-lookup purposes. `ageTurns` buckets are log2-spaced so a bucket\n * boundary corresponds to roughly one additional recency half-life having elapsed -- the point\n * at which the decayed score has plausibly moved enough to change ranking/selection -- rather\n * than an arbitrary fixed window.\n */\nfunction bucketAgeTurnsForCacheKeyV2(ageTurns: number | null | undefined): number | null {\n\tif (ageTurns === null || ageTurns === undefined || !Number.isFinite(ageTurns)) return null;\n\tif (ageTurns <= 0) return 0;\n\treturn Math.floor(Math.log2(ageTurns + 1));\n}\n\nfunction roundScoreForCacheKeyV2(score: number | null | undefined): number | null {\n\tif (score === null || score === undefined || Number.isNaN(score)) return null;\n\tif (!Number.isFinite(score)) return score; // preserve +/-Infinity (hard-pinned items)\n\treturn Math.round(score);\n}\n\nfunction roundUnitIntervalForCacheKeyV2(value: number | null | undefined): number | null {\n\tif (value === null || value === undefined || !Number.isFinite(value)) return null;\n\treturn Math.round(value * 10) / 10;\n}\n\nexport function buildContextBudgetPlanCacheKeyV2(input: {\n\treadonly keyBase: ContextBudgetCacheKeyBaseV2;\n\treadonly promptHash?: string;\n\treadonly maxTokens: number;\n\treadonly responseReserveTokens: number;\n\treadonly safetyMarginTokens: number;\n\treadonly availableTokens: number;\n\treadonly planned: readonly PlannedItemV2[];\n\treadonly tierPolicy: Readonly<Record<string, TierBudgetPolicyV2>>;\n\treadonly qualityPolicy: HeadroomQualityPolicyV2;\n}): string {\n\treturn `context-plan:${sha256Canonical({\n\t\tavailableTokens: input.availableTokens,\n\t\tbudgetBucket: input.keyBase.budgetBucket,\n\t\titems: input.planned\n\t\t\t.map((planned) => ({\n\t\t\t\tageTurns: bucketAgeTurnsForCacheKeyV2(planned.item.ageTurns),\n\t\t\t\tbaseScore: roundScoreForCacheKeyV2(planned.baseScore),\n\t\t\t\teffectiveScore: roundScoreForCacheKeyV2(planned.effectiveScore),\n\t\t\t\tevidenceKind: planned.item.evidenceKind ?? null,\n\t\t\t\tevidenceValue: planned.item.evidenceValue ?? null,\n\t\t\t\tid: planned.item.id,\n\t\t\t\tpinReason: planned.item.pinReason ?? null,\n\t\t\t\tpriority: planned.item.priority,\n\t\t\t\trecency: roundUnitIntervalForCacheKeyV2(planned.item.recency),\n\t\t\t\tredundancyKey: planned.item.redundancyKey ?? null,\n\t\t\t\tredundancyPenalty: roundScoreForCacheKeyV2(planned.redundancyPenalty),\n\t\t\t\trelevance: planned.item.relevance ?? null,\n\t\t\t\trepresentations: (planned.item.representations ?? [])\n\t\t\t\t\t.map((candidate) => ({\n\t\t\t\t\t\tfingerprint: computeContextBudgetRepresentationFingerprintV2(candidate),\n\t\t\t\t\t\tkind: candidate.kind,\n\t\t\t\t\t}))\n\t\t\t\t\t.sort((a, b) => a.kind.localeCompare(b.kind) || a.fingerprint.localeCompare(b.fingerprint)),\n\t\t\t\trequired: planned.item.required === true,\n\t\t\t\tsourceRef: fingerprintSourceRef(planned.item.sourceRef),\n\t\t\t\tsourceHash: planned.contentHash,\n\t\t\t\ttier: planned.item.tier,\n\t\t\t\ttokenEstimate: planned.item.tokenEstimate ?? null,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.id.localeCompare(b.id)),\n\t\tmaxTokens: input.maxTokens,\n\t\tinvalidationSnapshotHash: input.keyBase.invalidationSnapshotHash ?? \"none\",\n\t\tmodelId: input.keyBase.modelId,\n\t\tnamespace: input.keyBase.namespace,\n\t\tpolicyVersion: input.keyBase.policyVersion,\n\t\tpromptHash: input.promptHash ?? null,\n\t\tqueryIntentHash: input.keyBase.queryIntentHash,\n\t\tredactionPolicyHash: input.keyBase.redactionPolicyHash,\n\t\tresponseReserveTokens: input.responseReserveTokens,\n\t\tsafetyMarginTokens: input.safetyMarginTokens,\n\t\tsafetyProfileHash: input.keyBase.safetyProfileHash,\n\t\ttierPolicy: input.tierPolicy,\n\t\tqualityPolicy: input.qualityPolicy,\n\t\ttokenizerId: input.keyBase.tokenizerId,\n\t})}`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headroom.d.ts","sourceRoot":"","sources":["../../src/core/headroom.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"headroom.d.ts","sourceRoot":"","sources":["../../src/core/headroom.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CAChE;AAED,MAAM,WAAW,kBAAkB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC9C,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAChD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAO;IAEzB,MAAM,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAOvC;IAED,OAAO,IAAI,kBAAkB,CAoB5B;IAED,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CA6B5E;CACD;AAED,qBAAa,uBAAwB,SAAQ,YAAY;IACxD,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,SAAS,SAAQ,EAG5B;IAED,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAU5E;CACD;AAED,qBAAa,eAAgB,SAAQ,YAAY;IAChD,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,SAAS,SAAQ,EAK5B;IAED,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,gBAAgB,SAAI,EAAE,cAAc,SAAI,GAAG,gBAAgB,CAyBtF;IAED,OAAO,IAAI,kBAAkB,CAE5B;IAED,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAEjF;CACD;AAED,eAAe,eAAe,CAAC","sourcesContent":["// OMK v6.2 — Headroom Manager (TypeScript)\n// ==================================================================\n// packages/coding-agent/src/core/headroom.ts\n//\n// STATUS: compatibility utility.\n// This file is preserved for legacy extension/API consumers and regression tests.\n//\n// ACTUAL HEADROOM / TOKEN BUDGET SYSTEM:\n// - context-budget-token-counter.ts → token counting for budget decisions\n// - compaction/compaction.ts getCompactionHeadroomThreshold() → headroom threshold\n// - compaction/compaction.ts shouldCompact() → triggers compaction when headroom low\n//\n// context-budget-compressors.ts (headroom/llmlingua CLI compressor adapters) was removed\n// 2026-08-01: it had zero importers outside its own test file, i.e. it was never wired into\n// the real budget pipeline above despite this comment previously listing it as \"the actual\n// system\" -- that claim was stale/wrong. If CLI-based compression is wanted again, it needs\n// a real call site in the planner/governor before it's worth re-adding.\n//\n// Keep this utility deterministic and aligned with the active context budget policy.\n\nimport { EventEmitter } from \"events\";\n\nexport interface HeadroomSnapshot {\n\ttimestamp: number;\n\tpromptTokens: number;\n\ttoolOutputTokens: number;\n\tresponseTokens: number;\n\ttotalTokens: number;\n\theadroomRemaining: number;\n\tstatus: \"idle\" | \"active\" | \"stressed\" | \"critical\" | \"blocked\";\n}\n\nexport interface HeadroomPrediction {\n\tpredictedTokens: number;\n\tconfidence: number;\n\ttrend: \"increasing\" | \"stable\" | \"decreasing\";\n\trecommendedAction: string;\n}\n\nexport class HeadroomMonitor extends EventEmitter {\n\tprivate history: HeadroomSnapshot[] = [];\n\tprivate maxHistory = 100;\n\n\trecord(snapshot: HeadroomSnapshot): void {\n\t\tconst sanitized = sanitizeSnapshot(snapshot);\n\t\tthis.history.push(sanitized);\n\t\tif (this.history.length > this.maxHistory) {\n\t\t\tthis.history = this.history.slice(-this.maxHistory);\n\t\t}\n\t\tthis.emit(\"snapshot\", sanitized);\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\tif (this.history.length < 3) {\n\t\t\treturn { predictedTokens: 0, confidence: 0, trend: \"stable\", recommendedAction: \"collect more data\" };\n\t\t}\n\n\t\tconst recent = this.history.slice(-10);\n\t\tconst first = recent[0].totalTokens;\n\t\tconst last = recent[recent.length - 1].totalTokens;\n\t\tconst delta = last - first;\n\t\tconst slope = delta / Math.max(recent.length - 1, 1);\n\t\tconst trend = Math.abs(delta) <= Math.max(1, last * 0.02) ? \"stable\" : delta > 0 ? \"increasing\" : \"decreasing\";\n\t\tconst projectedTokens = trend === \"increasing\" ? last + Math.max(1, slope) : Math.max(0, last + slope);\n\t\tconst confidence = Math.min(0.95, 0.45 + recent.length * 0.05);\n\n\t\treturn {\n\t\t\tpredictedTokens: Math.round(projectedTokens),\n\t\t\tconfidence,\n\t\t\ttrend,\n\t\t\trecommendedAction: trend === \"increasing\" ? \"compress context\" : \"maintain\",\n\t\t};\n\t}\n\n\tgetStatus(totalTokens: number, maxTokens: number): HeadroomSnapshot[\"status\"] {\n\t\tconst normalizedTotalTokens = normalizeTokenInput(totalTokens);\n\t\tconst normalizedMaxTokens = normalizeTokenLimit(maxTokens);\n\t\tif (normalizedMaxTokens === 0) {\n\t\t\treturn normalizedTotalTokens === 0 ? \"idle\" : \"blocked\";\n\t\t}\n\n\t\tconst ratio = normalizedTotalTokens / normalizedMaxTokens;\n\t\tlet status: HeadroomSnapshot[\"status\"];\n\t\tif (ratio < 0.4) {\n\t\t\tstatus = \"idle\";\n\t\t} else if (ratio < 0.7) {\n\t\t\tstatus = \"active\";\n\t\t} else if (ratio < 0.8) {\n\t\t\tstatus = \"stressed\";\n\t\t} else if (ratio < 0.9) {\n\t\t\tstatus = \"critical\";\n\t\t} else {\n\t\t\tstatus = \"blocked\";\n\t\t}\n\n\t\tconst previous = this.history[this.history.length - 1]?.status;\n\t\tif (previous === \"critical\" && status === \"stressed\" && ratio >= 0.75) {\n\t\t\treturn \"critical\";\n\t\t}\n\t\tif (previous === \"blocked\" && status !== \"blocked\" && ratio >= 0.85) {\n\t\t\treturn \"blocked\";\n\t\t}\n\t\treturn status;\n\t}\n}\n\nexport class DynamicContextAllocator extends EventEmitter {\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t}\n\n\tallocate(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\tif (this.maxTokens === 0) {\n\t\t\treturn { maxTokens: 0, canAddAgents: false };\n\t\t}\n\t\tconst ratio = normalizeTokenInput(currentTokens) / this.maxTokens;\n\t\tif (ratio < 0.4) return { maxTokens: this.maxTokens, canAddAgents: true };\n\t\tif (ratio < 0.7) return { maxTokens: this.maxTokens * 0.9, canAddAgents: true };\n\t\tif (ratio < 0.8) return { maxTokens: this.maxTokens * 0.75, canAddAgents: false };\n\t\tif (ratio < 0.9) return { maxTokens: this.maxTokens * 0.5, canAddAgents: false };\n\t\treturn { maxTokens: 0, canAddAgents: false };\n\t}\n}\n\nexport class HeadroomManager extends EventEmitter {\n\tprivate monitor: HeadroomMonitor;\n\tprivate allocator: DynamicContextAllocator;\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t\tthis.monitor = new HeadroomMonitor();\n\t\tthis.allocator = new DynamicContextAllocator(this.maxTokens);\n\t}\n\n\tcheck(promptTokens: number, toolOutputTokens = 0, responseTokens = 0): HeadroomSnapshot {\n\t\tconst normalizedPromptTokens = normalizeTokenInput(promptTokens);\n\t\tconst normalizedToolOutputTokens = normalizeTokenInput(toolOutputTokens);\n\t\tconst normalizedResponseTokens = normalizeTokenInput(responseTokens);\n\t\tconst totalTokens = normalizedPromptTokens + normalizedToolOutputTokens + normalizedResponseTokens;\n\t\tconst status = this.monitor.getStatus(totalTokens, this.maxTokens);\n\t\tthis.allocator.allocate(totalTokens);\n\n\t\tconst snapshot: HeadroomSnapshot = {\n\t\t\ttimestamp: Date.now(),\n\t\t\tpromptTokens: normalizedPromptTokens,\n\t\t\ttoolOutputTokens: normalizedToolOutputTokens,\n\t\t\tresponseTokens: normalizedResponseTokens,\n\t\t\ttotalTokens,\n\t\t\theadroomRemaining: this.maxTokens - totalTokens,\n\t\t\tstatus,\n\t\t};\n\n\t\tthis.monitor.record(snapshot);\n\n\t\tif (status === \"critical\" || status === \"blocked\") {\n\t\t\tthis.emit(\"warning\", { status, message: `Headroom ${status}: ${totalTokens}/${this.maxTokens} tokens` });\n\t\t}\n\n\t\treturn snapshot;\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\treturn this.monitor.predict();\n\t}\n\n\tgetAllocation(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\treturn this.allocator.allocate(currentTokens);\n\t}\n}\n\nexport default HeadroomManager;\n\nfunction sanitizeSnapshot(snapshot: HeadroomSnapshot): HeadroomSnapshot {\n\treturn {\n\t\ttimestamp: Number.isFinite(snapshot.timestamp) ? Math.floor(snapshot.timestamp) : 0,\n\t\tpromptTokens: normalizeTokenInput(snapshot.promptTokens),\n\t\ttoolOutputTokens: normalizeTokenInput(snapshot.toolOutputTokens),\n\t\tresponseTokens: normalizeTokenInput(snapshot.responseTokens),\n\t\ttotalTokens: normalizeTokenInput(snapshot.totalTokens),\n\t\theadroomRemaining: normalizeFiniteInteger(snapshot.headroomRemaining),\n\t\tstatus: snapshot.status,\n\t};\n}\n\nfunction normalizeTokenLimit(value: number): number {\n\treturn normalizeTokenInput(value);\n}\n\nfunction normalizeTokenInput(value: number): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction normalizeFiniteInteger(value: number): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n"]}
|
package/dist/core/headroom.js
CHANGED
|
@@ -6,11 +6,16 @@
|
|
|
6
6
|
// This file is preserved for legacy extension/API consumers and regression tests.
|
|
7
7
|
//
|
|
8
8
|
// ACTUAL HEADROOM / TOKEN BUDGET SYSTEM:
|
|
9
|
-
// - context-budget-compressors.ts → context budget compression with adapters
|
|
10
9
|
// - context-budget-token-counter.ts → token counting for budget decisions
|
|
11
10
|
// - compaction/compaction.ts getCompactionHeadroomThreshold() → headroom threshold
|
|
12
11
|
// - compaction/compaction.ts shouldCompact() → triggers compaction when headroom low
|
|
13
12
|
//
|
|
13
|
+
// context-budget-compressors.ts (headroom/llmlingua CLI compressor adapters) was removed
|
|
14
|
+
// 2026-08-01: it had zero importers outside its own test file, i.e. it was never wired into
|
|
15
|
+
// the real budget pipeline above despite this comment previously listing it as "the actual
|
|
16
|
+
// system" -- that claim was stale/wrong. If CLI-based compression is wanted again, it needs
|
|
17
|
+
// a real call site in the planner/governor before it's worth re-adding.
|
|
18
|
+
//
|
|
14
19
|
// Keep this utility deterministic and aligned with the active context budget policy.
|
|
15
20
|
import { EventEmitter } from "events";
|
|
16
21
|
export class HeadroomMonitor extends EventEmitter {
|
|
@@ -66,7 +71,7 @@ export class HeadroomMonitor extends EventEmitter {
|
|
|
66
71
|
else {
|
|
67
72
|
status = "blocked";
|
|
68
73
|
}
|
|
69
|
-
const previous = this.history.
|
|
74
|
+
const previous = this.history[this.history.length - 1]?.status;
|
|
70
75
|
if (previous === "critical" && status === "stressed" && ratio >= 0.75) {
|
|
71
76
|
return "critical";
|
|
72
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headroom.js","sourceRoot":"","sources":["../../src/core/headroom.ts"],"names":[],"mappings":"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","sourcesContent":["// OMK v6.2 — Headroom Manager (TypeScript)\n// ==================================================================\n// packages/coding-agent/src/core/headroom.ts\n//\n// STATUS: compatibility utility.\n// This file is preserved for legacy extension/API consumers and regression tests.\n//\n// ACTUAL HEADROOM / TOKEN BUDGET SYSTEM:\n// - context-budget-compressors.ts → context budget compression with adapters\n// - context-budget-token-counter.ts → token counting for budget decisions\n// - compaction/compaction.ts getCompactionHeadroomThreshold() → headroom threshold\n// - compaction/compaction.ts shouldCompact() → triggers compaction when headroom low\n//\n// Keep this utility deterministic and aligned with the active context budget policy.\n\nimport { EventEmitter } from \"events\";\n\nexport interface HeadroomSnapshot {\n\ttimestamp: number;\n\tpromptTokens: number;\n\ttoolOutputTokens: number;\n\tresponseTokens: number;\n\ttotalTokens: number;\n\theadroomRemaining: number;\n\tstatus: \"idle\" | \"active\" | \"stressed\" | \"critical\" | \"blocked\";\n}\n\nexport interface HeadroomPrediction {\n\tpredictedTokens: number;\n\tconfidence: number;\n\ttrend: \"increasing\" | \"stable\" | \"decreasing\";\n\trecommendedAction: string;\n}\n\nexport class HeadroomMonitor extends EventEmitter {\n\tprivate history: HeadroomSnapshot[] = [];\n\tprivate maxHistory = 100;\n\n\trecord(snapshot: HeadroomSnapshot): void {\n\t\tconst sanitized = sanitizeSnapshot(snapshot);\n\t\tthis.history.push(sanitized);\n\t\tif (this.history.length > this.maxHistory) {\n\t\t\tthis.history = this.history.slice(-this.maxHistory);\n\t\t}\n\t\tthis.emit(\"snapshot\", sanitized);\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\tif (this.history.length < 3) {\n\t\t\treturn { predictedTokens: 0, confidence: 0, trend: \"stable\", recommendedAction: \"collect more data\" };\n\t\t}\n\n\t\tconst recent = this.history.slice(-10);\n\t\tconst first = recent[0].totalTokens;\n\t\tconst last = recent[recent.length - 1].totalTokens;\n\t\tconst delta = last - first;\n\t\tconst slope = delta / Math.max(recent.length - 1, 1);\n\t\tconst trend = Math.abs(delta) <= Math.max(1, last * 0.02) ? \"stable\" : delta > 0 ? \"increasing\" : \"decreasing\";\n\t\tconst projectedTokens = trend === \"increasing\" ? last + Math.max(1, slope) : Math.max(0, last + slope);\n\t\tconst confidence = Math.min(0.95, 0.45 + recent.length * 0.05);\n\n\t\treturn {\n\t\t\tpredictedTokens: Math.round(projectedTokens),\n\t\t\tconfidence,\n\t\t\ttrend,\n\t\t\trecommendedAction: trend === \"increasing\" ? \"compress context\" : \"maintain\",\n\t\t};\n\t}\n\n\tgetStatus(totalTokens: number, maxTokens: number): HeadroomSnapshot[\"status\"] {\n\t\tconst normalizedTotalTokens = normalizeTokenInput(totalTokens);\n\t\tconst normalizedMaxTokens = normalizeTokenLimit(maxTokens);\n\t\tif (normalizedMaxTokens === 0) {\n\t\t\treturn normalizedTotalTokens === 0 ? \"idle\" : \"blocked\";\n\t\t}\n\n\t\tconst ratio = normalizedTotalTokens / normalizedMaxTokens;\n\t\tlet status: HeadroomSnapshot[\"status\"];\n\t\tif (ratio < 0.4) {\n\t\t\tstatus = \"idle\";\n\t\t} else if (ratio < 0.7) {\n\t\t\tstatus = \"active\";\n\t\t} else if (ratio < 0.8) {\n\t\t\tstatus = \"stressed\";\n\t\t} else if (ratio < 0.9) {\n\t\t\tstatus = \"critical\";\n\t\t} else {\n\t\t\tstatus = \"blocked\";\n\t\t}\n\n\t\tconst previous = this.history.at(-1)?.status;\n\t\tif (previous === \"critical\" && status === \"stressed\" && ratio >= 0.75) {\n\t\t\treturn \"critical\";\n\t\t}\n\t\tif (previous === \"blocked\" && status !== \"blocked\" && ratio >= 0.85) {\n\t\t\treturn \"blocked\";\n\t\t}\n\t\treturn status;\n\t}\n}\n\nexport class DynamicContextAllocator extends EventEmitter {\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t}\n\n\tallocate(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\tif (this.maxTokens === 0) {\n\t\t\treturn { maxTokens: 0, canAddAgents: false };\n\t\t}\n\t\tconst ratio = normalizeTokenInput(currentTokens) / this.maxTokens;\n\t\tif (ratio < 0.4) return { maxTokens: this.maxTokens, canAddAgents: true };\n\t\tif (ratio < 0.7) return { maxTokens: this.maxTokens * 0.9, canAddAgents: true };\n\t\tif (ratio < 0.8) return { maxTokens: this.maxTokens * 0.75, canAddAgents: false };\n\t\tif (ratio < 0.9) return { maxTokens: this.maxTokens * 0.5, canAddAgents: false };\n\t\treturn { maxTokens: 0, canAddAgents: false };\n\t}\n}\n\nexport class HeadroomManager extends EventEmitter {\n\tprivate monitor: HeadroomMonitor;\n\tprivate allocator: DynamicContextAllocator;\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t\tthis.monitor = new HeadroomMonitor();\n\t\tthis.allocator = new DynamicContextAllocator(this.maxTokens);\n\t}\n\n\tcheck(promptTokens: number, toolOutputTokens = 0, responseTokens = 0): HeadroomSnapshot {\n\t\tconst normalizedPromptTokens = normalizeTokenInput(promptTokens);\n\t\tconst normalizedToolOutputTokens = normalizeTokenInput(toolOutputTokens);\n\t\tconst normalizedResponseTokens = normalizeTokenInput(responseTokens);\n\t\tconst totalTokens = normalizedPromptTokens + normalizedToolOutputTokens + normalizedResponseTokens;\n\t\tconst status = this.monitor.getStatus(totalTokens, this.maxTokens);\n\t\tthis.allocator.allocate(totalTokens);\n\n\t\tconst snapshot: HeadroomSnapshot = {\n\t\t\ttimestamp: Date.now(),\n\t\t\tpromptTokens: normalizedPromptTokens,\n\t\t\ttoolOutputTokens: normalizedToolOutputTokens,\n\t\t\tresponseTokens: normalizedResponseTokens,\n\t\t\ttotalTokens,\n\t\t\theadroomRemaining: this.maxTokens - totalTokens,\n\t\t\tstatus,\n\t\t};\n\n\t\tthis.monitor.record(snapshot);\n\n\t\tif (status === \"critical\" || status === \"blocked\") {\n\t\t\tthis.emit(\"warning\", { status, message: `Headroom ${status}: ${totalTokens}/${this.maxTokens} tokens` });\n\t\t}\n\n\t\treturn snapshot;\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\treturn this.monitor.predict();\n\t}\n\n\tgetAllocation(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\treturn this.allocator.allocate(currentTokens);\n\t}\n}\n\nexport default HeadroomManager;\n\nfunction sanitizeSnapshot(snapshot: HeadroomSnapshot): HeadroomSnapshot {\n\treturn {\n\t\ttimestamp: Number.isFinite(snapshot.timestamp) ? Math.floor(snapshot.timestamp) : 0,\n\t\tpromptTokens: normalizeTokenInput(snapshot.promptTokens),\n\t\ttoolOutputTokens: normalizeTokenInput(snapshot.toolOutputTokens),\n\t\tresponseTokens: normalizeTokenInput(snapshot.responseTokens),\n\t\ttotalTokens: normalizeTokenInput(snapshot.totalTokens),\n\t\theadroomRemaining: normalizeFiniteInteger(snapshot.headroomRemaining),\n\t\tstatus: snapshot.status,\n\t};\n}\n\nfunction normalizeTokenLimit(value: number): number {\n\treturn normalizeTokenInput(value);\n}\n\nfunction normalizeTokenInput(value: number): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction normalizeFiniteInteger(value: number): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"headroom.js","sourceRoot":"","sources":["../../src/core/headroom.ts"],"names":[],"mappings":"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","sourcesContent":["// OMK v6.2 — Headroom Manager (TypeScript)\n// ==================================================================\n// packages/coding-agent/src/core/headroom.ts\n//\n// STATUS: compatibility utility.\n// This file is preserved for legacy extension/API consumers and regression tests.\n//\n// ACTUAL HEADROOM / TOKEN BUDGET SYSTEM:\n// - context-budget-token-counter.ts → token counting for budget decisions\n// - compaction/compaction.ts getCompactionHeadroomThreshold() → headroom threshold\n// - compaction/compaction.ts shouldCompact() → triggers compaction when headroom low\n//\n// context-budget-compressors.ts (headroom/llmlingua CLI compressor adapters) was removed\n// 2026-08-01: it had zero importers outside its own test file, i.e. it was never wired into\n// the real budget pipeline above despite this comment previously listing it as \"the actual\n// system\" -- that claim was stale/wrong. If CLI-based compression is wanted again, it needs\n// a real call site in the planner/governor before it's worth re-adding.\n//\n// Keep this utility deterministic and aligned with the active context budget policy.\n\nimport { EventEmitter } from \"events\";\n\nexport interface HeadroomSnapshot {\n\ttimestamp: number;\n\tpromptTokens: number;\n\ttoolOutputTokens: number;\n\tresponseTokens: number;\n\ttotalTokens: number;\n\theadroomRemaining: number;\n\tstatus: \"idle\" | \"active\" | \"stressed\" | \"critical\" | \"blocked\";\n}\n\nexport interface HeadroomPrediction {\n\tpredictedTokens: number;\n\tconfidence: number;\n\ttrend: \"increasing\" | \"stable\" | \"decreasing\";\n\trecommendedAction: string;\n}\n\nexport class HeadroomMonitor extends EventEmitter {\n\tprivate history: HeadroomSnapshot[] = [];\n\tprivate maxHistory = 100;\n\n\trecord(snapshot: HeadroomSnapshot): void {\n\t\tconst sanitized = sanitizeSnapshot(snapshot);\n\t\tthis.history.push(sanitized);\n\t\tif (this.history.length > this.maxHistory) {\n\t\t\tthis.history = this.history.slice(-this.maxHistory);\n\t\t}\n\t\tthis.emit(\"snapshot\", sanitized);\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\tif (this.history.length < 3) {\n\t\t\treturn { predictedTokens: 0, confidence: 0, trend: \"stable\", recommendedAction: \"collect more data\" };\n\t\t}\n\n\t\tconst recent = this.history.slice(-10);\n\t\tconst first = recent[0].totalTokens;\n\t\tconst last = recent[recent.length - 1].totalTokens;\n\t\tconst delta = last - first;\n\t\tconst slope = delta / Math.max(recent.length - 1, 1);\n\t\tconst trend = Math.abs(delta) <= Math.max(1, last * 0.02) ? \"stable\" : delta > 0 ? \"increasing\" : \"decreasing\";\n\t\tconst projectedTokens = trend === \"increasing\" ? last + Math.max(1, slope) : Math.max(0, last + slope);\n\t\tconst confidence = Math.min(0.95, 0.45 + recent.length * 0.05);\n\n\t\treturn {\n\t\t\tpredictedTokens: Math.round(projectedTokens),\n\t\t\tconfidence,\n\t\t\ttrend,\n\t\t\trecommendedAction: trend === \"increasing\" ? \"compress context\" : \"maintain\",\n\t\t};\n\t}\n\n\tgetStatus(totalTokens: number, maxTokens: number): HeadroomSnapshot[\"status\"] {\n\t\tconst normalizedTotalTokens = normalizeTokenInput(totalTokens);\n\t\tconst normalizedMaxTokens = normalizeTokenLimit(maxTokens);\n\t\tif (normalizedMaxTokens === 0) {\n\t\t\treturn normalizedTotalTokens === 0 ? \"idle\" : \"blocked\";\n\t\t}\n\n\t\tconst ratio = normalizedTotalTokens / normalizedMaxTokens;\n\t\tlet status: HeadroomSnapshot[\"status\"];\n\t\tif (ratio < 0.4) {\n\t\t\tstatus = \"idle\";\n\t\t} else if (ratio < 0.7) {\n\t\t\tstatus = \"active\";\n\t\t} else if (ratio < 0.8) {\n\t\t\tstatus = \"stressed\";\n\t\t} else if (ratio < 0.9) {\n\t\t\tstatus = \"critical\";\n\t\t} else {\n\t\t\tstatus = \"blocked\";\n\t\t}\n\n\t\tconst previous = this.history[this.history.length - 1]?.status;\n\t\tif (previous === \"critical\" && status === \"stressed\" && ratio >= 0.75) {\n\t\t\treturn \"critical\";\n\t\t}\n\t\tif (previous === \"blocked\" && status !== \"blocked\" && ratio >= 0.85) {\n\t\t\treturn \"blocked\";\n\t\t}\n\t\treturn status;\n\t}\n}\n\nexport class DynamicContextAllocator extends EventEmitter {\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t}\n\n\tallocate(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\tif (this.maxTokens === 0) {\n\t\t\treturn { maxTokens: 0, canAddAgents: false };\n\t\t}\n\t\tconst ratio = normalizeTokenInput(currentTokens) / this.maxTokens;\n\t\tif (ratio < 0.4) return { maxTokens: this.maxTokens, canAddAgents: true };\n\t\tif (ratio < 0.7) return { maxTokens: this.maxTokens * 0.9, canAddAgents: true };\n\t\tif (ratio < 0.8) return { maxTokens: this.maxTokens * 0.75, canAddAgents: false };\n\t\tif (ratio < 0.9) return { maxTokens: this.maxTokens * 0.5, canAddAgents: false };\n\t\treturn { maxTokens: 0, canAddAgents: false };\n\t}\n}\n\nexport class HeadroomManager extends EventEmitter {\n\tprivate monitor: HeadroomMonitor;\n\tprivate allocator: DynamicContextAllocator;\n\tprivate maxTokens: number;\n\n\tconstructor(maxTokens = 15000) {\n\t\tsuper();\n\t\tthis.maxTokens = normalizeTokenLimit(maxTokens);\n\t\tthis.monitor = new HeadroomMonitor();\n\t\tthis.allocator = new DynamicContextAllocator(this.maxTokens);\n\t}\n\n\tcheck(promptTokens: number, toolOutputTokens = 0, responseTokens = 0): HeadroomSnapshot {\n\t\tconst normalizedPromptTokens = normalizeTokenInput(promptTokens);\n\t\tconst normalizedToolOutputTokens = normalizeTokenInput(toolOutputTokens);\n\t\tconst normalizedResponseTokens = normalizeTokenInput(responseTokens);\n\t\tconst totalTokens = normalizedPromptTokens + normalizedToolOutputTokens + normalizedResponseTokens;\n\t\tconst status = this.monitor.getStatus(totalTokens, this.maxTokens);\n\t\tthis.allocator.allocate(totalTokens);\n\n\t\tconst snapshot: HeadroomSnapshot = {\n\t\t\ttimestamp: Date.now(),\n\t\t\tpromptTokens: normalizedPromptTokens,\n\t\t\ttoolOutputTokens: normalizedToolOutputTokens,\n\t\t\tresponseTokens: normalizedResponseTokens,\n\t\t\ttotalTokens,\n\t\t\theadroomRemaining: this.maxTokens - totalTokens,\n\t\t\tstatus,\n\t\t};\n\n\t\tthis.monitor.record(snapshot);\n\n\t\tif (status === \"critical\" || status === \"blocked\") {\n\t\t\tthis.emit(\"warning\", { status, message: `Headroom ${status}: ${totalTokens}/${this.maxTokens} tokens` });\n\t\t}\n\n\t\treturn snapshot;\n\t}\n\n\tpredict(): HeadroomPrediction {\n\t\treturn this.monitor.predict();\n\t}\n\n\tgetAllocation(currentTokens: number): { maxTokens: number; canAddAgents: boolean } {\n\t\treturn this.allocator.allocate(currentTokens);\n\t}\n}\n\nexport default HeadroomManager;\n\nfunction sanitizeSnapshot(snapshot: HeadroomSnapshot): HeadroomSnapshot {\n\treturn {\n\t\ttimestamp: Number.isFinite(snapshot.timestamp) ? Math.floor(snapshot.timestamp) : 0,\n\t\tpromptTokens: normalizeTokenInput(snapshot.promptTokens),\n\t\ttoolOutputTokens: normalizeTokenInput(snapshot.toolOutputTokens),\n\t\tresponseTokens: normalizeTokenInput(snapshot.responseTokens),\n\t\ttotalTokens: normalizeTokenInput(snapshot.totalTokens),\n\t\theadroomRemaining: normalizeFiniteInteger(snapshot.headroomRemaining),\n\t\tstatus: snapshot.status,\n\t};\n}\n\nfunction normalizeTokenLimit(value: number): number {\n\treturn normalizeTokenInput(value);\n}\n\nfunction normalizeTokenInput(value: number): number {\n\tif (!Number.isFinite(value) || value < 0) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction normalizeFiniteInteger(value: number): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\treturn Math.floor(value);\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-custom-provider-anthropic",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-custom-provider-anthropic",
|
|
9
|
-
"version": "0.95.
|
|
9
|
+
"version": "0.95.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-gondolin",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-gondolin",
|
|
9
|
-
"version": "0.95.
|
|
9
|
+
"version": "0.95.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@earendil-works/gondolin": "0.12.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-sandbox",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-sandbox",
|
|
9
|
-
"version": "0.95.
|
|
9
|
+
"version": "0.95.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "0.0.26"
|
|
12
12
|
}
|
|
@@ -10,7 +10,7 @@ You are a planning specialist. You receive context (from a scout) and requiremen
|
|
|
10
10
|
You must NOT make any changes. Only read, analyze, and plan.
|
|
11
11
|
|
|
12
12
|
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
13
|
-
- Skills: ulw-plan
|
|
13
|
+
- Skills: ulw-plan, packages, deep-interview
|
|
14
14
|
|
|
15
15
|
Input format you'll receive:
|
|
16
16
|
- Context/findings from a scout agent
|
|
@@ -11,7 +11,7 @@ Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT m
|
|
|
11
11
|
Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
|
|
12
12
|
|
|
13
13
|
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
14
|
-
- Skills: review-work
|
|
14
|
+
- Skills: review-work, security-review
|
|
15
15
|
|
|
16
16
|
Strategy:
|
|
17
17
|
1. Run `git diff` to see recent changes (if applicable)
|
|
@@ -10,7 +10,7 @@ You are a scout. Quickly investigate a codebase and return structured findings t
|
|
|
10
10
|
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
11
11
|
|
|
12
12
|
Assigned capabilities (see ~/.omk/agent/SUBAGENTS.md for the full routing reference):
|
|
13
|
-
- Skills: understand-anything
|
|
13
|
+
- Skills: understand-anything, ast-grep, headroom
|
|
14
14
|
|
|
15
15
|
Thoroughness (infer from task, default medium):
|
|
16
16
|
- Quick: Targeted lookups, key files only
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-extension-with-deps",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omk-extension-with-deps",
|
|
9
|
-
"version": "0.95.
|
|
9
|
+
"version": "0.95.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "2.1.3"
|
|
12
12
|
},
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-multi-agent-kit",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "open-multi-agent-kit",
|
|
9
|
-
"version": "0.95.
|
|
9
|
+
"version": "0.95.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"omk-agent-core": "^0.95.
|
|
13
|
-
"omk-ai": "^0.95.
|
|
14
|
-
"omk-tui": "^0.95.
|
|
12
|
+
"omk-agent-core": "^0.95.1",
|
|
13
|
+
"omk-ai": "^0.95.1",
|
|
14
|
+
"omk-tui": "^0.95.1",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"typebox": "1.1.38",
|
|
27
27
|
"undici": "8.5.0",
|
|
28
28
|
"yaml": "2.9.0",
|
|
29
|
-
"omk-adaptorch-wpl": "^0.95.
|
|
29
|
+
"omk-adaptorch-wpl": "^0.95.1"
|
|
30
30
|
},
|
|
31
31
|
"optionalDependencies": {
|
|
32
32
|
"@mariozechner/clipboard": "0.3.9"
|
|
@@ -1424,19 +1424,19 @@
|
|
|
1424
1424
|
}
|
|
1425
1425
|
},
|
|
1426
1426
|
"node_modules/omk-adaptorch-wpl": {
|
|
1427
|
-
"version": "0.95.
|
|
1428
|
-
"resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.95.
|
|
1427
|
+
"version": "0.95.1",
|
|
1428
|
+
"resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.95.1.tgz",
|
|
1429
1429
|
"license": "MIT",
|
|
1430
1430
|
"engines": {
|
|
1431
1431
|
"node": ">=22.19.0"
|
|
1432
1432
|
}
|
|
1433
1433
|
},
|
|
1434
1434
|
"node_modules/omk-agent-core": {
|
|
1435
|
-
"version": "0.95.
|
|
1436
|
-
"resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.95.
|
|
1435
|
+
"version": "0.95.1",
|
|
1436
|
+
"resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.95.1.tgz",
|
|
1437
1437
|
"license": "MIT",
|
|
1438
1438
|
"dependencies": {
|
|
1439
|
-
"omk-ai": "^0.95.
|
|
1439
|
+
"omk-ai": "^0.95.1",
|
|
1440
1440
|
"ignore": "7.0.5",
|
|
1441
1441
|
"typebox": "1.1.38",
|
|
1442
1442
|
"yaml": "2.9.0"
|
|
@@ -1446,8 +1446,8 @@
|
|
|
1446
1446
|
}
|
|
1447
1447
|
},
|
|
1448
1448
|
"node_modules/omk-ai": {
|
|
1449
|
-
"version": "0.95.
|
|
1450
|
-
"resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.95.
|
|
1449
|
+
"version": "0.95.1",
|
|
1450
|
+
"resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.95.1.tgz",
|
|
1451
1451
|
"license": "MIT",
|
|
1452
1452
|
"dependencies": {
|
|
1453
1453
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -1469,8 +1469,8 @@
|
|
|
1469
1469
|
}
|
|
1470
1470
|
},
|
|
1471
1471
|
"node_modules/omk-tui": {
|
|
1472
|
-
"version": "0.95.
|
|
1473
|
-
"resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.95.
|
|
1472
|
+
"version": "0.95.1",
|
|
1473
|
+
"resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.95.1.tgz",
|
|
1474
1474
|
"license": "MIT",
|
|
1475
1475
|
"dependencies": {
|
|
1476
1476
|
"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.95.
|
|
3
|
+
"version": "0.95.1",
|
|
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": {
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"postinstall": "node ./scripts/link-workspace-deps.mjs"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"omk-agent-core": "^0.95.
|
|
48
|
-
"omk-ai": "^0.95.
|
|
49
|
-
"omk-tui": "^0.95.
|
|
47
|
+
"omk-agent-core": "^0.95.1",
|
|
48
|
+
"omk-ai": "^0.95.1",
|
|
49
|
+
"omk-tui": "^0.95.1",
|
|
50
50
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
51
51
|
"chalk": "5.6.2",
|
|
52
52
|
"cross-spawn": "7.0.6",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typebox": "1.1.38",
|
|
62
62
|
"undici": "8.5.0",
|
|
63
63
|
"yaml": "2.9.0",
|
|
64
|
-
"omk-adaptorch-wpl": "^0.95.
|
|
64
|
+
"omk-adaptorch-wpl": "^0.95.1"
|
|
65
65
|
},
|
|
66
66
|
"overrides": {
|
|
67
67
|
"rimraf": "6.1.2",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { type TokenCounterAdapter, type TokenCountResult } from "./context-budget-token-counter.ts";
|
|
2
|
-
export type ContextBudgetCompressorMode = "off" | "auto" | "headroom" | "llmlingua";
|
|
3
|
-
export type ContextBudgetCompressionMethod = "compressed" | "none";
|
|
4
|
-
export interface ContextCompressionRequest {
|
|
5
|
-
readonly text: string;
|
|
6
|
-
readonly targetTokens: number;
|
|
7
|
-
readonly modelId: string;
|
|
8
|
-
readonly timeoutMs?: number;
|
|
9
|
-
readonly maxOutputChars?: number;
|
|
10
|
-
}
|
|
11
|
-
export interface ContextCompressionResult {
|
|
12
|
-
readonly text: string;
|
|
13
|
-
readonly method: ContextBudgetCompressionMethod;
|
|
14
|
-
readonly adapterId: string;
|
|
15
|
-
readonly input: TokenCountResult;
|
|
16
|
-
readonly output: TokenCountResult;
|
|
17
|
-
readonly notes: readonly string[];
|
|
18
|
-
}
|
|
19
|
-
export interface ContextCompressorAdapter {
|
|
20
|
-
readonly id: string;
|
|
21
|
-
readonly priority: number;
|
|
22
|
-
isAvailable(): Promise<boolean>;
|
|
23
|
-
compress(request: ContextCompressionRequest): Promise<ContextCompressionResult>;
|
|
24
|
-
}
|
|
25
|
-
export interface CommandRunRequest {
|
|
26
|
-
readonly command: string;
|
|
27
|
-
readonly args: readonly string[];
|
|
28
|
-
readonly input: string;
|
|
29
|
-
readonly timeoutMs: number;
|
|
30
|
-
}
|
|
31
|
-
export interface CommandRunResult {
|
|
32
|
-
readonly exitCode: number | null;
|
|
33
|
-
readonly stdout: string;
|
|
34
|
-
readonly stderr: string;
|
|
35
|
-
readonly timedOut: boolean;
|
|
36
|
-
}
|
|
37
|
-
export interface CommandRunner {
|
|
38
|
-
run(request: CommandRunRequest): Promise<CommandRunResult>;
|
|
39
|
-
}
|
|
40
|
-
export interface CliCompressorOptions {
|
|
41
|
-
readonly command?: string;
|
|
42
|
-
readonly runner?: CommandRunner;
|
|
43
|
-
readonly tokenCounter?: TokenCounterAdapter;
|
|
44
|
-
}
|
|
45
|
-
export declare function createNodeCommandRunner(): CommandRunner;
|
|
46
|
-
export declare function createHeadroomCliCompressor(options?: CliCompressorOptions): ContextCompressorAdapter;
|
|
47
|
-
export declare function createLlmlinguaCliCompressor(options?: CliCompressorOptions): ContextCompressorAdapter;
|
|
48
|
-
export declare function createContextCompressorForMode(mode: ContextBudgetCompressorMode, options?: CliCompressorOptions): ContextCompressorAdapter | undefined;
|
|
49
|
-
export declare function createCompressorRegistry(adapters: readonly ContextCompressorAdapter[]): ContextCompressorAdapter;
|
|
50
|
-
//# sourceMappingURL=context-budget-compressors.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-compressors.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-compressors.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,MAAM,mCAAmC,CAAC;AAE3C,MAAM,MAAM,2BAA2B,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;AACpF,MAAM,MAAM,8BAA8B,GAAG,YAAY,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,8BAA8B,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;CAC5C;AAKD,wBAAgB,uBAAuB,IAAI,aAAa,CAqCvD;AAED,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,oBAAyB,GAAG,wBAAwB,CASxG;AAED,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,oBAAyB,GAAG,wBAAwB,CASzG;AAED,wBAAgB,8BAA8B,CAC7C,IAAI,EAAE,2BAA2B,EACjC,OAAO,GAAE,oBAAyB,GAChC,wBAAwB,GAAG,SAAS,CAWtC;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAAG,wBAAwB,CAwBhH","sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n\tcreateFallbackTokenCounter,\n\ttype TokenCounterAdapter,\n\ttype TokenCountResult,\n} from \"./context-budget-token-counter.ts\";\n\nexport type ContextBudgetCompressorMode = \"off\" | \"auto\" | \"headroom\" | \"llmlingua\";\nexport type ContextBudgetCompressionMethod = \"compressed\" | \"none\";\n\nexport interface ContextCompressionRequest {\n\treadonly text: string;\n\treadonly targetTokens: number;\n\treadonly modelId: string;\n\treadonly timeoutMs?: number;\n\treadonly maxOutputChars?: number;\n}\n\nexport interface ContextCompressionResult {\n\treadonly text: string;\n\treadonly method: ContextBudgetCompressionMethod;\n\treadonly adapterId: string;\n\treadonly input: TokenCountResult;\n\treadonly output: TokenCountResult;\n\treadonly notes: readonly string[];\n}\n\nexport interface ContextCompressorAdapter {\n\treadonly id: string;\n\treadonly priority: number;\n\tisAvailable(): Promise<boolean>;\n\tcompress(request: ContextCompressionRequest): Promise<ContextCompressionResult>;\n}\n\nexport interface CommandRunRequest {\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly input: string;\n\treadonly timeoutMs: number;\n}\n\nexport interface CommandRunResult {\n\treadonly exitCode: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\treadonly timedOut: boolean;\n}\n\nexport interface CommandRunner {\n\trun(request: CommandRunRequest): Promise<CommandRunResult>;\n}\n\nexport interface CliCompressorOptions {\n\treadonly command?: string;\n\treadonly runner?: CommandRunner;\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 400_000;\n\nexport function createNodeCommandRunner(): CommandRunner {\n\treturn {\n\t\trun(request) {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tconst child = spawn(request.command, [...request.args], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\t\t\tlet stdout = \"\";\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tlet settled = false;\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\tresolve({ exitCode: null, stdout, stderr, timedOut: true });\n\t\t\t\t}, request.timeoutMs);\n\t\t\t\tchild.stdout.setEncoding(\"utf8\");\n\t\t\t\tchild.stderr.setEncoding(\"utf8\");\n\t\t\t\tchild.stdout.on(\"data\", (chunk: string) => {\n\t\t\t\t\tstdout += chunk;\n\t\t\t\t});\n\t\t\t\tchild.stderr.on(\"data\", (chunk: string) => {\n\t\t\t\t\tstderr += chunk;\n\t\t\t\t});\n\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve({ exitCode: null, stdout, stderr: error.message, timedOut: false });\n\t\t\t\t});\n\t\t\t\tchild.on(\"close\", (exitCode) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve({ exitCode, stdout, stderr, timedOut: false });\n\t\t\t\t});\n\t\t\t\tchild.stdin.end(request.input, \"utf8\");\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport function createHeadroomCliCompressor(options: CliCompressorOptions = {}): ContextCompressorAdapter {\n\treturn createCliCompressor({\n\t\tid: \"headroom-cli\",\n\t\tpriority: 80,\n\t\tcommand: options.command ?? \"headroom\",\n\t\targs: (request) => [\"compress\", \"--target-tokens\", String(request.targetTokens), \"--model\", request.modelId],\n\t\trunner: options.runner,\n\t\ttokenCounter: options.tokenCounter,\n\t});\n}\n\nexport function createLlmlinguaCliCompressor(options: CliCompressorOptions = {}): ContextCompressorAdapter {\n\treturn createCliCompressor({\n\t\tid: \"llmlingua-cli\",\n\t\tpriority: 60,\n\t\tcommand: options.command ?? \"llmlingua\",\n\t\targs: (request) => [\"compress\", \"--target-tokens\", String(request.targetTokens), \"--model\", request.modelId],\n\t\trunner: options.runner,\n\t\ttokenCounter: options.tokenCounter,\n\t});\n}\n\nexport function createContextCompressorForMode(\n\tmode: ContextBudgetCompressorMode,\n\toptions: CliCompressorOptions = {},\n): ContextCompressorAdapter | undefined {\n\tif (mode === \"off\") {\n\t\treturn undefined;\n\t}\n\tif (mode === \"headroom\") {\n\t\treturn createHeadroomCliCompressor(options);\n\t}\n\tif (mode === \"llmlingua\") {\n\t\treturn createLlmlinguaCliCompressor(options);\n\t}\n\treturn createCompressorRegistry([createHeadroomCliCompressor(options), createLlmlinguaCliCompressor(options)]);\n}\n\nexport function createCompressorRegistry(adapters: readonly ContextCompressorAdapter[]): ContextCompressorAdapter {\n\tconst ordered = [...adapters].sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));\n\treturn {\n\t\tid: \"context-compressor-registry\",\n\t\tpriority: 100,\n\t\tisAvailable: async () => true,\n\t\tasync compress(request) {\n\t\t\tconst notes: string[] = [];\n\t\t\tfor (const adapter of ordered) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!(await adapter.isAvailable())) {\n\t\t\t\t\t\tnotes.push(`${adapter.id}:unavailable`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = await adapter.compress(request);\n\t\t\t\t\treturn { ...result, notes: [...notes, ...result.notes] };\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : \"unknown compressor failure\";\n\t\t\t\t\tnotes.push(`${adapter.id}:failed:${message}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn createNoCompressionResult(request, createFallbackTokenCounter(), notes);\n\t\t},\n\t};\n}\n\ninterface CliCompressorInternalOptions {\n\treadonly id: string;\n\treadonly priority: number;\n\treadonly command: string;\n\treadonly args: (request: ContextCompressionRequest) => readonly string[];\n\treadonly runner?: CommandRunner;\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nfunction createCliCompressor(options: CliCompressorInternalOptions): ContextCompressorAdapter {\n\tconst runner = options.runner ?? createNodeCommandRunner();\n\tconst tokenCounter = options.tokenCounter ?? createFallbackTokenCounter();\n\treturn {\n\t\tid: options.id,\n\t\tpriority: options.priority,\n\t\tasync isAvailable() {\n\t\t\tconst result = await runner.run({\n\t\t\t\tcommand: options.command,\n\t\t\t\targs: [\"--version\"],\n\t\t\t\tinput: \"\",\n\t\t\t\ttimeoutMs: 3_000,\n\t\t\t});\n\t\t\treturn result.exitCode === 0 && !result.timedOut;\n\t\t},\n\t\tasync compress(request) {\n\t\t\tconst input = tokenCounter.countText(request.text, request.modelId);\n\t\t\tif (input.tokens <= request.targetTokens) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:already-within-budget`]);\n\t\t\t}\n\t\t\tconst timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\t\tconst result = await runner.run({\n\t\t\t\tcommand: options.command,\n\t\t\t\targs: options.args(request),\n\t\t\t\tinput: request.text,\n\t\t\t\ttimeoutMs,\n\t\t\t});\n\t\t\tif (result.timedOut) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:timeout`]);\n\t\t\t}\n\t\t\tif (result.exitCode !== 0) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [\n\t\t\t\t\t`${options.id}:exit:${result.exitCode ?? \"null\"}`,\n\t\t\t\t]);\n\t\t\t}\n\t\t\tconst maxOutputChars = request.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;\n\t\t\tconst outputText = result.stdout.slice(0, maxOutputChars);\n\t\t\tif (outputText.trim().length === 0) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:empty-output`]);\n\t\t\t}\n\t\t\tconst output = tokenCounter.countText(outputText, request.modelId);\n\t\t\treturn {\n\t\t\t\ttext: outputText,\n\t\t\t\tmethod: \"compressed\",\n\t\t\t\tadapterId: options.id,\n\t\t\t\tinput,\n\t\t\t\toutput,\n\t\t\t\tnotes: outputText.length < result.stdout.length ? [`${options.id}:output-truncated`] : [`${options.id}:ok`],\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction createNoCompressionResult(\n\trequest: ContextCompressionRequest,\n\ttokenCounter: TokenCounterAdapter,\n\tnotes: readonly string[],\n): ContextCompressionResult {\n\tconst input = tokenCounter.countText(request.text, request.modelId);\n\treturn {\n\t\ttext: request.text,\n\t\tmethod: \"none\",\n\t\tadapterId: \"none\",\n\t\tinput,\n\t\toutput: input,\n\t\tnotes,\n\t};\n}\n"]}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { createFallbackTokenCounter, } from "./context-budget-token-counter.js";
|
|
3
|
-
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
4
|
-
const DEFAULT_MAX_OUTPUT_CHARS = 400_000;
|
|
5
|
-
export function createNodeCommandRunner() {
|
|
6
|
-
return {
|
|
7
|
-
run(request) {
|
|
8
|
-
return new Promise((resolve) => {
|
|
9
|
-
const child = spawn(request.command, [...request.args], { stdio: ["pipe", "pipe", "pipe"] });
|
|
10
|
-
let stdout = "";
|
|
11
|
-
let stderr = "";
|
|
12
|
-
let settled = false;
|
|
13
|
-
const timeout = setTimeout(() => {
|
|
14
|
-
settled = true;
|
|
15
|
-
child.kill("SIGTERM");
|
|
16
|
-
resolve({ exitCode: null, stdout, stderr, timedOut: true });
|
|
17
|
-
}, request.timeoutMs);
|
|
18
|
-
child.stdout.setEncoding("utf8");
|
|
19
|
-
child.stderr.setEncoding("utf8");
|
|
20
|
-
child.stdout.on("data", (chunk) => {
|
|
21
|
-
stdout += chunk;
|
|
22
|
-
});
|
|
23
|
-
child.stderr.on("data", (chunk) => {
|
|
24
|
-
stderr += chunk;
|
|
25
|
-
});
|
|
26
|
-
child.on("error", (error) => {
|
|
27
|
-
if (settled)
|
|
28
|
-
return;
|
|
29
|
-
settled = true;
|
|
30
|
-
clearTimeout(timeout);
|
|
31
|
-
resolve({ exitCode: null, stdout, stderr: error.message, timedOut: false });
|
|
32
|
-
});
|
|
33
|
-
child.on("close", (exitCode) => {
|
|
34
|
-
if (settled)
|
|
35
|
-
return;
|
|
36
|
-
settled = true;
|
|
37
|
-
clearTimeout(timeout);
|
|
38
|
-
resolve({ exitCode, stdout, stderr, timedOut: false });
|
|
39
|
-
});
|
|
40
|
-
child.stdin.end(request.input, "utf8");
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export function createHeadroomCliCompressor(options = {}) {
|
|
46
|
-
return createCliCompressor({
|
|
47
|
-
id: "headroom-cli",
|
|
48
|
-
priority: 80,
|
|
49
|
-
command: options.command ?? "headroom",
|
|
50
|
-
args: (request) => ["compress", "--target-tokens", String(request.targetTokens), "--model", request.modelId],
|
|
51
|
-
runner: options.runner,
|
|
52
|
-
tokenCounter: options.tokenCounter,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
export function createLlmlinguaCliCompressor(options = {}) {
|
|
56
|
-
return createCliCompressor({
|
|
57
|
-
id: "llmlingua-cli",
|
|
58
|
-
priority: 60,
|
|
59
|
-
command: options.command ?? "llmlingua",
|
|
60
|
-
args: (request) => ["compress", "--target-tokens", String(request.targetTokens), "--model", request.modelId],
|
|
61
|
-
runner: options.runner,
|
|
62
|
-
tokenCounter: options.tokenCounter,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
export function createContextCompressorForMode(mode, options = {}) {
|
|
66
|
-
if (mode === "off") {
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
if (mode === "headroom") {
|
|
70
|
-
return createHeadroomCliCompressor(options);
|
|
71
|
-
}
|
|
72
|
-
if (mode === "llmlingua") {
|
|
73
|
-
return createLlmlinguaCliCompressor(options);
|
|
74
|
-
}
|
|
75
|
-
return createCompressorRegistry([createHeadroomCliCompressor(options), createLlmlinguaCliCompressor(options)]);
|
|
76
|
-
}
|
|
77
|
-
export function createCompressorRegistry(adapters) {
|
|
78
|
-
const ordered = [...adapters].sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));
|
|
79
|
-
return {
|
|
80
|
-
id: "context-compressor-registry",
|
|
81
|
-
priority: 100,
|
|
82
|
-
isAvailable: async () => true,
|
|
83
|
-
async compress(request) {
|
|
84
|
-
const notes = [];
|
|
85
|
-
for (const adapter of ordered) {
|
|
86
|
-
try {
|
|
87
|
-
if (!(await adapter.isAvailable())) {
|
|
88
|
-
notes.push(`${adapter.id}:unavailable`);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const result = await adapter.compress(request);
|
|
92
|
-
return { ...result, notes: [...notes, ...result.notes] };
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
const message = error instanceof Error ? error.message : "unknown compressor failure";
|
|
96
|
-
notes.push(`${adapter.id}:failed:${message}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return createNoCompressionResult(request, createFallbackTokenCounter(), notes);
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function createCliCompressor(options) {
|
|
104
|
-
const runner = options.runner ?? createNodeCommandRunner();
|
|
105
|
-
const tokenCounter = options.tokenCounter ?? createFallbackTokenCounter();
|
|
106
|
-
return {
|
|
107
|
-
id: options.id,
|
|
108
|
-
priority: options.priority,
|
|
109
|
-
async isAvailable() {
|
|
110
|
-
const result = await runner.run({
|
|
111
|
-
command: options.command,
|
|
112
|
-
args: ["--version"],
|
|
113
|
-
input: "",
|
|
114
|
-
timeoutMs: 3_000,
|
|
115
|
-
});
|
|
116
|
-
return result.exitCode === 0 && !result.timedOut;
|
|
117
|
-
},
|
|
118
|
-
async compress(request) {
|
|
119
|
-
const input = tokenCounter.countText(request.text, request.modelId);
|
|
120
|
-
if (input.tokens <= request.targetTokens) {
|
|
121
|
-
return createNoCompressionResult(request, tokenCounter, [`${options.id}:already-within-budget`]);
|
|
122
|
-
}
|
|
123
|
-
const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
124
|
-
const result = await runner.run({
|
|
125
|
-
command: options.command,
|
|
126
|
-
args: options.args(request),
|
|
127
|
-
input: request.text,
|
|
128
|
-
timeoutMs,
|
|
129
|
-
});
|
|
130
|
-
if (result.timedOut) {
|
|
131
|
-
return createNoCompressionResult(request, tokenCounter, [`${options.id}:timeout`]);
|
|
132
|
-
}
|
|
133
|
-
if (result.exitCode !== 0) {
|
|
134
|
-
return createNoCompressionResult(request, tokenCounter, [
|
|
135
|
-
`${options.id}:exit:${result.exitCode ?? "null"}`,
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
const maxOutputChars = request.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
139
|
-
const outputText = result.stdout.slice(0, maxOutputChars);
|
|
140
|
-
if (outputText.trim().length === 0) {
|
|
141
|
-
return createNoCompressionResult(request, tokenCounter, [`${options.id}:empty-output`]);
|
|
142
|
-
}
|
|
143
|
-
const output = tokenCounter.countText(outputText, request.modelId);
|
|
144
|
-
return {
|
|
145
|
-
text: outputText,
|
|
146
|
-
method: "compressed",
|
|
147
|
-
adapterId: options.id,
|
|
148
|
-
input,
|
|
149
|
-
output,
|
|
150
|
-
notes: outputText.length < result.stdout.length ? [`${options.id}:output-truncated`] : [`${options.id}:ok`],
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function createNoCompressionResult(request, tokenCounter, notes) {
|
|
156
|
-
const input = tokenCounter.countText(request.text, request.modelId);
|
|
157
|
-
return {
|
|
158
|
-
text: request.text,
|
|
159
|
-
method: "none",
|
|
160
|
-
adapterId: "none",
|
|
161
|
-
input,
|
|
162
|
-
output: input,
|
|
163
|
-
notes,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
//# sourceMappingURL=context-budget-compressors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-compressors.js","sourceRoot":"","sources":["../../src/core/context-budget-compressors.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n\tcreateFallbackTokenCounter,\n\ttype TokenCounterAdapter,\n\ttype TokenCountResult,\n} from \"./context-budget-token-counter.ts\";\n\nexport type ContextBudgetCompressorMode = \"off\" | \"auto\" | \"headroom\" | \"llmlingua\";\nexport type ContextBudgetCompressionMethod = \"compressed\" | \"none\";\n\nexport interface ContextCompressionRequest {\n\treadonly text: string;\n\treadonly targetTokens: number;\n\treadonly modelId: string;\n\treadonly timeoutMs?: number;\n\treadonly maxOutputChars?: number;\n}\n\nexport interface ContextCompressionResult {\n\treadonly text: string;\n\treadonly method: ContextBudgetCompressionMethod;\n\treadonly adapterId: string;\n\treadonly input: TokenCountResult;\n\treadonly output: TokenCountResult;\n\treadonly notes: readonly string[];\n}\n\nexport interface ContextCompressorAdapter {\n\treadonly id: string;\n\treadonly priority: number;\n\tisAvailable(): Promise<boolean>;\n\tcompress(request: ContextCompressionRequest): Promise<ContextCompressionResult>;\n}\n\nexport interface CommandRunRequest {\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly input: string;\n\treadonly timeoutMs: number;\n}\n\nexport interface CommandRunResult {\n\treadonly exitCode: number | null;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\treadonly timedOut: boolean;\n}\n\nexport interface CommandRunner {\n\trun(request: CommandRunRequest): Promise<CommandRunResult>;\n}\n\nexport interface CliCompressorOptions {\n\treadonly command?: string;\n\treadonly runner?: CommandRunner;\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 400_000;\n\nexport function createNodeCommandRunner(): CommandRunner {\n\treturn {\n\t\trun(request) {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tconst child = spawn(request.command, [...request.args], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\t\t\tlet stdout = \"\";\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tlet settled = false;\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\tresolve({ exitCode: null, stdout, stderr, timedOut: true });\n\t\t\t\t}, request.timeoutMs);\n\t\t\t\tchild.stdout.setEncoding(\"utf8\");\n\t\t\t\tchild.stderr.setEncoding(\"utf8\");\n\t\t\t\tchild.stdout.on(\"data\", (chunk: string) => {\n\t\t\t\t\tstdout += chunk;\n\t\t\t\t});\n\t\t\t\tchild.stderr.on(\"data\", (chunk: string) => {\n\t\t\t\t\tstderr += chunk;\n\t\t\t\t});\n\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve({ exitCode: null, stdout, stderr: error.message, timedOut: false });\n\t\t\t\t});\n\t\t\t\tchild.on(\"close\", (exitCode) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve({ exitCode, stdout, stderr, timedOut: false });\n\t\t\t\t});\n\t\t\t\tchild.stdin.end(request.input, \"utf8\");\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport function createHeadroomCliCompressor(options: CliCompressorOptions = {}): ContextCompressorAdapter {\n\treturn createCliCompressor({\n\t\tid: \"headroom-cli\",\n\t\tpriority: 80,\n\t\tcommand: options.command ?? \"headroom\",\n\t\targs: (request) => [\"compress\", \"--target-tokens\", String(request.targetTokens), \"--model\", request.modelId],\n\t\trunner: options.runner,\n\t\ttokenCounter: options.tokenCounter,\n\t});\n}\n\nexport function createLlmlinguaCliCompressor(options: CliCompressorOptions = {}): ContextCompressorAdapter {\n\treturn createCliCompressor({\n\t\tid: \"llmlingua-cli\",\n\t\tpriority: 60,\n\t\tcommand: options.command ?? \"llmlingua\",\n\t\targs: (request) => [\"compress\", \"--target-tokens\", String(request.targetTokens), \"--model\", request.modelId],\n\t\trunner: options.runner,\n\t\ttokenCounter: options.tokenCounter,\n\t});\n}\n\nexport function createContextCompressorForMode(\n\tmode: ContextBudgetCompressorMode,\n\toptions: CliCompressorOptions = {},\n): ContextCompressorAdapter | undefined {\n\tif (mode === \"off\") {\n\t\treturn undefined;\n\t}\n\tif (mode === \"headroom\") {\n\t\treturn createHeadroomCliCompressor(options);\n\t}\n\tif (mode === \"llmlingua\") {\n\t\treturn createLlmlinguaCliCompressor(options);\n\t}\n\treturn createCompressorRegistry([createHeadroomCliCompressor(options), createLlmlinguaCliCompressor(options)]);\n}\n\nexport function createCompressorRegistry(adapters: readonly ContextCompressorAdapter[]): ContextCompressorAdapter {\n\tconst ordered = [...adapters].sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));\n\treturn {\n\t\tid: \"context-compressor-registry\",\n\t\tpriority: 100,\n\t\tisAvailable: async () => true,\n\t\tasync compress(request) {\n\t\t\tconst notes: string[] = [];\n\t\t\tfor (const adapter of ordered) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!(await adapter.isAvailable())) {\n\t\t\t\t\t\tnotes.push(`${adapter.id}:unavailable`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = await adapter.compress(request);\n\t\t\t\t\treturn { ...result, notes: [...notes, ...result.notes] };\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : \"unknown compressor failure\";\n\t\t\t\t\tnotes.push(`${adapter.id}:failed:${message}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn createNoCompressionResult(request, createFallbackTokenCounter(), notes);\n\t\t},\n\t};\n}\n\ninterface CliCompressorInternalOptions {\n\treadonly id: string;\n\treadonly priority: number;\n\treadonly command: string;\n\treadonly args: (request: ContextCompressionRequest) => readonly string[];\n\treadonly runner?: CommandRunner;\n\treadonly tokenCounter?: TokenCounterAdapter;\n}\n\nfunction createCliCompressor(options: CliCompressorInternalOptions): ContextCompressorAdapter {\n\tconst runner = options.runner ?? createNodeCommandRunner();\n\tconst tokenCounter = options.tokenCounter ?? createFallbackTokenCounter();\n\treturn {\n\t\tid: options.id,\n\t\tpriority: options.priority,\n\t\tasync isAvailable() {\n\t\t\tconst result = await runner.run({\n\t\t\t\tcommand: options.command,\n\t\t\t\targs: [\"--version\"],\n\t\t\t\tinput: \"\",\n\t\t\t\ttimeoutMs: 3_000,\n\t\t\t});\n\t\t\treturn result.exitCode === 0 && !result.timedOut;\n\t\t},\n\t\tasync compress(request) {\n\t\t\tconst input = tokenCounter.countText(request.text, request.modelId);\n\t\t\tif (input.tokens <= request.targetTokens) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:already-within-budget`]);\n\t\t\t}\n\t\t\tconst timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\t\tconst result = await runner.run({\n\t\t\t\tcommand: options.command,\n\t\t\t\targs: options.args(request),\n\t\t\t\tinput: request.text,\n\t\t\t\ttimeoutMs,\n\t\t\t});\n\t\t\tif (result.timedOut) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:timeout`]);\n\t\t\t}\n\t\t\tif (result.exitCode !== 0) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [\n\t\t\t\t\t`${options.id}:exit:${result.exitCode ?? \"null\"}`,\n\t\t\t\t]);\n\t\t\t}\n\t\t\tconst maxOutputChars = request.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;\n\t\t\tconst outputText = result.stdout.slice(0, maxOutputChars);\n\t\t\tif (outputText.trim().length === 0) {\n\t\t\t\treturn createNoCompressionResult(request, tokenCounter, [`${options.id}:empty-output`]);\n\t\t\t}\n\t\t\tconst output = tokenCounter.countText(outputText, request.modelId);\n\t\t\treturn {\n\t\t\t\ttext: outputText,\n\t\t\t\tmethod: \"compressed\",\n\t\t\t\tadapterId: options.id,\n\t\t\t\tinput,\n\t\t\t\toutput,\n\t\t\t\tnotes: outputText.length < result.stdout.length ? [`${options.id}:output-truncated`] : [`${options.id}:ok`],\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction createNoCompressionResult(\n\trequest: ContextCompressionRequest,\n\ttokenCounter: TokenCounterAdapter,\n\tnotes: readonly string[],\n): ContextCompressionResult {\n\tconst input = tokenCounter.countText(request.text, request.modelId);\n\treturn {\n\t\ttext: request.text,\n\t\tmethod: \"none\",\n\t\tadapterId: \"none\",\n\t\tinput,\n\t\toutput: input,\n\t\tnotes,\n\t};\n}\n"]}
|