@cgh567/agent 2.4.1 → 2.4.2
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/bin/helios +0 -0
- package/bin/helios-rpc-node-wrapper.cjs +0 -0
- package/bin/helios-rpc-wrapper.sh +0 -0
- package/daemon/adapters/helios-rpc-adapter.js +47 -25
- package/daemon/config/com.familiar.helios-daemon.plist +5 -0
- package/daemon/config/helios-daemon.service +4 -0
- package/daemon/context-enrichment.js +59 -21
- package/daemon/helios-api.js +149 -37
- package/daemon/helios-company-daemon.js +516 -124
- package/daemon/lib/harada/cascade-judge.js +12 -50
- package/daemon/lib/harada/mandala.js +20 -0
- package/daemon/lib/harada/pillar-dispatcher.js +1 -1
- package/daemon/lib/harada/project-factory.js +7 -2
- package/daemon/lib/hbo-bridge.js +31 -12
- package/daemon/lib/helios-hitl-host.js +15 -2
- package/daemon/lib/hitl-interaction-service.js +0 -0
- package/daemon/lib/memgraph-verify.js +38 -33
- package/daemon/lib/project-drift-detector.js +7 -17
- package/daemon/lib/project-semantic-updater.js +1 -14
- package/daemon/routes/channels.js +10 -5
- package/daemon/routes/harada-map.js +11 -48
- package/daemon/routes/hbo.js +89 -28
- package/daemon/routes/hitl.js +0 -0
- package/daemon/routes/project.js +4 -3
- package/daemon/routes/wizard.js +11 -4
- package/daemon/schema-migrations-hitl.js +0 -0
- package/extensions/001-tool-output-cap.ts +0 -0
- package/extensions/context-compaction.ts +45 -26
- package/extensions/cortex/activation-bridge.ts +5 -0
- package/extensions/cortex/learn.ts +26 -0
- package/extensions/email/backfill.ts +0 -0
- package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
- package/extensions/helios-governance/analysis/compliance.ts +0 -0
- package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
- package/extensions/helios-governance/analysis/output-contract.ts +0 -0
- package/extensions/helios-governance/analysis/patterns.ts +0 -0
- package/extensions/helios-governance/analysis/preflight.ts +0 -0
- package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
- package/extensions/helios-governance/analysis/task-classification.ts +0 -0
- package/extensions/helios-governance/analysis/task-intent.ts +0 -0
- package/extensions/helios-governance/gates/high-impact.ts +1 -1
- package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
- package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
- package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
- package/extensions/hema-dispatch-v3/index.ts +59 -40
- package/extensions/lib/elo-engine.js +0 -0
- package/extensions/lib/elo-engine.test.js +0 -0
- package/extensions/memgraph-autostart.ts +13 -0
- package/extensions/neuroplastic-eval.ts +0 -0
- package/extensions/shadow-loop/index.ts +0 -0
- package/lib/brain-v2-budget.js +0 -0
- package/lib/brain-v2-circuit-breaker.js +0 -0
- package/lib/brain-v2.js +0 -0
- package/lib/broker/adaptive-throttle.js +0 -0
- package/lib/broker/batch-coalescer.js +0 -0
- package/lib/broker/bulkhead.js +0 -0
- package/lib/broker/channel-registry.js +0 -0
- package/lib/broker/circuit-breaker.js +0 -0
- package/lib/broker/evidence-cache.js +0 -0
- package/lib/broker/health-monitor.js +0 -0
- package/lib/broker/mage-queue.js +0 -0
- package/lib/broker/priority-queue.js +0 -0
- package/lib/broker/server.js.bak-error2-fix +0 -0
- package/lib/broker/session-registry.js +0 -0
- package/lib/broker/singleton-timers.js +0 -0
- package/lib/broker/types.d.ts +0 -0
- package/lib/broker/vegas-limit.js +0 -0
- package/lib/compression/dist/ccr-store.js +74 -0
- package/lib/compression/dist/content-router.js +115 -0
- package/lib/compression/dist/pipeline.js +113 -0
- package/lib/compression/dist/server.js +265 -0
- package/lib/compression/dist/smart-crusher.js +251 -0
- package/lib/context-budget.ts +0 -0
- package/lib/context-firewall.js +0 -0
- package/lib/crm/integration/triage-bridge.js +0 -0
- package/lib/email-utils.ts +0 -0
- package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
- package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
- package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
- package/lib/eval/index.ts +0 -0
- package/lib/eval/preflight-checker.ts +0 -0
- package/lib/eval/task-domain-classifier.ts +0 -0
- package/lib/eval/task-instruction-parser.ts +0 -0
- package/lib/eval/verifier-runner.ts +0 -0
- package/lib/event-bus.d.ts +0 -0
- package/lib/governance-context-selector.ts +0 -0
- package/lib/graph/generate-extension-embeddings.js +0 -0
- package/lib/graph/generate-static-embeddings.js +0 -0
- package/lib/graph/lib/utils.js +1 -1
- package/lib/graph-audit.d.ts +0 -0
- package/lib/mesh-circuit-breaker.js +0 -0
- package/lib/mission-loop/lesson-extractor.ts +0 -0
- package/lib/mission-loop/mental-model-scorer.ts +0 -0
- package/lib/mission-loop/occ-detector.ts +0 -0
- package/lib/mission-loop/query-variants.ts +0 -0
- package/lib/mission-loop/verifier-check.ts +0 -0
- package/lib/skill-reference-builder.ts +0 -0
- package/lib/telemetry/token-breakdown.ts +0 -0
- package/lib/tool-compressor.ts +0 -0
- package/lib/triage-core/legal-routing.ts +0 -0
- package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
- package/lib/triage-core/mental-model/enrich-all.ts +0 -0
- package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
- package/lib/triage-core/mental-model/key-facts.ts +0 -0
- package/lib/triage-core/mental-model/model-assembler.ts +0 -0
- package/lib/triage-core/orchestrator.ts +0 -0
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
- package/package.json +10 -4
- package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
- package/skills/talisman-ceo/SKILL.md +23 -25
- package/skills/talisman-comms/SKILL.md +5 -5
- package/skills/talisman-engineering/SKILL.md +5 -5
- package/skills/talisman-finance/SKILL.md +10 -8
- package/skills/talisman-marketing/SKILL.md +10 -10
- package/skills/talisman-sales/SKILL.md +12 -15
- package/skills/talisman-support/SKILL.md +5 -5
- package/agents/business/talisman-ceo.md +0 -183
- package/agents/business/talisman-comms.md +0 -257
- package/agents/business/talisman-cto.md +0 -153
- package/agents/business/talisman-finance.md +0 -246
- package/agents/business/talisman-marketing.md +0 -240
- package/agents/business/talisman-sales.md +0 -242
- package/agents/business/talisman-support.md +0 -236
- package/daemon/lib/approval-expiry.js +0 -162
- package/daemon/lib/blast-radius-analyzer.js +0 -75
- package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
- package/daemon/lib/forensic-log.js +0 -113
- package/daemon/lib/goal-research-pipeline.js +0 -644
- package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
- package/daemon/lib/headroom-middleware.js +0 -167
- package/daemon/lib/headroom-proxy-manager.js +0 -623
- package/daemon/lib/hed-engine.js +0 -307
- package/daemon/lib/mental-model-cache.js +0 -96
- package/daemon/lib/project-factory.js +0 -47
- package/daemon/lib/session-log-reader.js +0 -93
- package/daemon/routes/hed.js +0 -133
- package/lib/graph/learning/headroom-learn-bridge.js +0 -215
- package/skills/helios-bookkeeping/SKILL.md +0 -321
- package/skills/helios-briefer/SKILL.md +0 -44
- package/skills/helios-client-relations/SKILL.md +0 -322
- package/skills/helios-personal-triager/SKILL.md +0 -45
- package/skills/helios-recruitment/SKILL.md +0 -317
- package/skills/helios-relationship-nudger/SKILL.md +0 -77
- package/skills/helios-researcher/SKILL.md +0 -44
- package/skills/helios-scheduler/SKILL.md +0 -58
- package/skills/helios-tax-analyst/SKILL.md +0 -280
|
File without changes
|
package/lib/brain-v2-budget.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/brain-v2.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/bulkhead.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/mage-queue.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/types.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CcrStore = void 0;
|
|
4
|
+
exports.getCcrStore = getCcrStore;
|
|
5
|
+
class CcrStore {
|
|
6
|
+
store = new Map();
|
|
7
|
+
ttlMs;
|
|
8
|
+
cleanupInterval = null;
|
|
9
|
+
constructor(ttlSeconds = 7200) {
|
|
10
|
+
this.ttlMs = ttlSeconds * 1000;
|
|
11
|
+
// Cleanup every 10 minutes
|
|
12
|
+
this.cleanupInterval = setInterval(() => this._cleanup(), 10 * 60 * 1000);
|
|
13
|
+
// Don't hold the event loop open
|
|
14
|
+
if (this.cleanupInterval.unref)
|
|
15
|
+
this.cleanupInterval.unref();
|
|
16
|
+
}
|
|
17
|
+
set(hash, data) {
|
|
18
|
+
this.store.set(hash, {
|
|
19
|
+
data,
|
|
20
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
21
|
+
originalBytes: data.length,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
get(hash) {
|
|
25
|
+
const entry = this.store.get(hash);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return null;
|
|
28
|
+
if (Date.now() > entry.expiresAt) {
|
|
29
|
+
this.store.delete(hash);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return entry.data;
|
|
33
|
+
}
|
|
34
|
+
delete(hash) {
|
|
35
|
+
this.store.delete(hash);
|
|
36
|
+
}
|
|
37
|
+
has(hash) {
|
|
38
|
+
return this.get(hash) !== null;
|
|
39
|
+
}
|
|
40
|
+
stats() {
|
|
41
|
+
let totalOriginalBytes = 0;
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
let entries = 0;
|
|
44
|
+
for (const [, entry] of this.store) {
|
|
45
|
+
if (now <= entry.expiresAt) {
|
|
46
|
+
totalOriginalBytes += entry.originalBytes;
|
|
47
|
+
entries++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { entries, totalOriginalBytes };
|
|
51
|
+
}
|
|
52
|
+
_cleanup() {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [hash, entry] of this.store) {
|
|
55
|
+
if (now > entry.expiresAt)
|
|
56
|
+
this.store.delete(hash);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
destroy() {
|
|
60
|
+
if (this.cleanupInterval) {
|
|
61
|
+
clearInterval(this.cleanupInterval);
|
|
62
|
+
this.cleanupInterval = null;
|
|
63
|
+
}
|
|
64
|
+
this.store.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.CcrStore = CcrStore;
|
|
68
|
+
// Singleton for the daemon process
|
|
69
|
+
let _singleton = null;
|
|
70
|
+
function getCcrStore(ttlSeconds = 7200) {
|
|
71
|
+
if (!_singleton)
|
|
72
|
+
_singleton = new CcrStore(ttlSeconds);
|
|
73
|
+
return _singleton;
|
|
74
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentRouter = void 0;
|
|
4
|
+
exports.detectContentType = detectContentType;
|
|
5
|
+
/**
|
|
6
|
+
* lib/compression/content-router.ts
|
|
7
|
+
*
|
|
8
|
+
* ContentRouter: detects content type in a message block and routes to the
|
|
9
|
+
* appropriate compressor.
|
|
10
|
+
*
|
|
11
|
+
* Current compressors:
|
|
12
|
+
* JSON_ARRAY → SmartCrusher (70–92% reduction)
|
|
13
|
+
* PLAIN_TEXT → Passthrough (Kompress-base ML model not available in TS)
|
|
14
|
+
* SOURCE_CODE → Passthrough (CodeCompressor not yet implemented)
|
|
15
|
+
* other → Passthrough
|
|
16
|
+
*
|
|
17
|
+
* The passthrough path is correct behavior — we improve compression as we
|
|
18
|
+
* add more compressors. SmartCrusher alone covers the dominant HBO payload
|
|
19
|
+
* type (arrays of lead/signal/task/goal objects).
|
|
20
|
+
*
|
|
21
|
+
* Protection rules (content never compressed):
|
|
22
|
+
* - User messages (content might be misinterpreted if rewritten)
|
|
23
|
+
* - Error outputs ≤ 8000 chars (must be preserved exactly)
|
|
24
|
+
* - Tool outputs named 'headroom_retrieve' (infinite loop prevention)
|
|
25
|
+
* - Content shorter than 500 chars (overhead > savings)
|
|
26
|
+
*/
|
|
27
|
+
const smart_crusher_js_1 = require("./smart-crusher.js");
|
|
28
|
+
const CHARS_PER_TOKEN = 3.5; // Claude/Bedrock heuristic
|
|
29
|
+
function estimateTokens(s) {
|
|
30
|
+
return Math.ceil(s.length / CHARS_PER_TOKEN);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detect content type from a string block.
|
|
34
|
+
*/
|
|
35
|
+
function detectContentType(text) {
|
|
36
|
+
const trimmed = text.trim();
|
|
37
|
+
// JSON array: starts with [
|
|
38
|
+
if (trimmed.startsWith('[')) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(trimmed);
|
|
41
|
+
if (Array.isArray(parsed))
|
|
42
|
+
return 'JSON_ARRAY';
|
|
43
|
+
}
|
|
44
|
+
catch { /* not valid JSON */ }
|
|
45
|
+
}
|
|
46
|
+
// JSON object: starts with {
|
|
47
|
+
if (trimmed.startsWith('{')) {
|
|
48
|
+
try {
|
|
49
|
+
JSON.parse(trimmed);
|
|
50
|
+
return 'JSON_OBJECT';
|
|
51
|
+
}
|
|
52
|
+
catch { /* not valid JSON */ }
|
|
53
|
+
}
|
|
54
|
+
// Source code heuristics (backtick code blocks, common keywords)
|
|
55
|
+
if (trimmed.startsWith('```') ||
|
|
56
|
+
trimmed.includes('\ndef ') ||
|
|
57
|
+
trimmed.includes('\nfunction ') ||
|
|
58
|
+
trimmed.includes('\nconst ') ||
|
|
59
|
+
trimmed.includes('\nclass ') ||
|
|
60
|
+
trimmed.includes('\nimport ')) {
|
|
61
|
+
return 'SOURCE_CODE';
|
|
62
|
+
}
|
|
63
|
+
return 'PLAIN_TEXT';
|
|
64
|
+
}
|
|
65
|
+
class ContentRouter {
|
|
66
|
+
crusher;
|
|
67
|
+
constructor(crusherConfig = {}) {
|
|
68
|
+
this.crusher = new smart_crusher_js_1.SmartCrusher(crusherConfig);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Route a single text block to the appropriate compressor.
|
|
72
|
+
* Returns the (possibly compressed) text plus metadata.
|
|
73
|
+
*/
|
|
74
|
+
route(text, toolId, ccrStore) {
|
|
75
|
+
const passthrough = {
|
|
76
|
+
output: text,
|
|
77
|
+
contentType: 'UNKNOWN',
|
|
78
|
+
strategy: 'passthrough',
|
|
79
|
+
compressed: false,
|
|
80
|
+
ccrHash: null,
|
|
81
|
+
tokensSaved: 0,
|
|
82
|
+
rowsDropped: 0,
|
|
83
|
+
};
|
|
84
|
+
// Protection: too short
|
|
85
|
+
if (text.length < 500)
|
|
86
|
+
return passthrough;
|
|
87
|
+
// Protection: never re-compress CCR retrieve results
|
|
88
|
+
if (toolId === 'headroom_retrieve')
|
|
89
|
+
return passthrough;
|
|
90
|
+
// Protection: small error outputs
|
|
91
|
+
if (text.length <= 8000 &&
|
|
92
|
+
(text.toLowerCase().includes('"error"') || text.toLowerCase().includes('"traceback"'))) {
|
|
93
|
+
return passthrough;
|
|
94
|
+
}
|
|
95
|
+
const contentType = detectContentType(text);
|
|
96
|
+
switch (contentType) {
|
|
97
|
+
case 'JSON_ARRAY': {
|
|
98
|
+
const result = this.crusher.compress(text, ccrStore);
|
|
99
|
+
const tokensSaved = estimateTokens(text) - estimateTokens(result.output);
|
|
100
|
+
return {
|
|
101
|
+
output: result.output,
|
|
102
|
+
contentType,
|
|
103
|
+
strategy: result.strategy,
|
|
104
|
+
compressed: result.compressed,
|
|
105
|
+
ccrHash: result.ccrHash,
|
|
106
|
+
tokensSaved: Math.max(0, tokensSaved),
|
|
107
|
+
rowsDropped: result.rowsDropped,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
return { ...passthrough, contentType };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.ContentRouter = ContentRouter;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompressionPipeline = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* lib/compression/pipeline.ts
|
|
6
|
+
*
|
|
7
|
+
* Message compression pipeline.
|
|
8
|
+
*
|
|
9
|
+
* Processes a messages array (Anthropic or OpenAI format) through the
|
|
10
|
+
* ContentRouter for each tool_result block.
|
|
11
|
+
*
|
|
12
|
+
* The pipeline mirrors what the headroom Python proxy does:
|
|
13
|
+
* 1. Skip system messages (cache prefix must stay stable)
|
|
14
|
+
* 2. Skip user messages (content must be preserved)
|
|
15
|
+
* 3. For each assistant + tool_result message: run ContentRouter
|
|
16
|
+
* 4. Collect total tokens saved and CCR hashes
|
|
17
|
+
*
|
|
18
|
+
* This runs inside the helios-agent daemon process — no HTTP round-trip.
|
|
19
|
+
* Every company's HBO tool results (signals, leads, pipeline, tasks) are
|
|
20
|
+
* compressed before they reach the LLM.
|
|
21
|
+
*/
|
|
22
|
+
const content_router_js_1 = require("./content-router.js");
|
|
23
|
+
/**
|
|
24
|
+
* Deep-copy a messages array (shallow copy of each message, deep copy of
|
|
25
|
+
* content arrays so we don't mutate the caller's data).
|
|
26
|
+
*/
|
|
27
|
+
function deepCopyMessages(messages) {
|
|
28
|
+
return messages.map((msg) => ({
|
|
29
|
+
...msg,
|
|
30
|
+
content: Array.isArray(msg.content)
|
|
31
|
+
? msg.content.map((b) => ({ ...b }))
|
|
32
|
+
: msg.content,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function extractText(content) {
|
|
36
|
+
if (!content)
|
|
37
|
+
return '';
|
|
38
|
+
if (typeof content === 'string')
|
|
39
|
+
return content;
|
|
40
|
+
for (const block of content) {
|
|
41
|
+
if (typeof block.content === 'string')
|
|
42
|
+
return block.content;
|
|
43
|
+
if (typeof block.text === 'string')
|
|
44
|
+
return block.text;
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
function originalLength(messages) {
|
|
49
|
+
return JSON.stringify(messages).length;
|
|
50
|
+
}
|
|
51
|
+
class CompressionPipeline {
|
|
52
|
+
router;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.router = new content_router_js_1.ContentRouter();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compress a messages array.
|
|
58
|
+
*
|
|
59
|
+
* Compresses tool_result blocks in ALL messages (user and assistant).
|
|
60
|
+
* In Anthropic format, tool_result blocks appear in user messages (they
|
|
61
|
+
* are the response to the assistant's tool_use calls). These are the
|
|
62
|
+
* dominant source of large payloads — HBO signals, leads, pipeline data.
|
|
63
|
+
*
|
|
64
|
+
* What we do NOT compress:
|
|
65
|
+
* - Plain text content in user messages (user intent must be preserved)
|
|
66
|
+
* - System messages (prompt cache stability)
|
|
67
|
+
* - Anything shorter than 500 chars
|
|
68
|
+
*/
|
|
69
|
+
compress(messages, ccrStore) {
|
|
70
|
+
const origLen = originalLength(messages);
|
|
71
|
+
const copied = deepCopyMessages(messages);
|
|
72
|
+
let totalTokensSaved = 0;
|
|
73
|
+
const ccrHashes = [];
|
|
74
|
+
const transformsApplied = new Set();
|
|
75
|
+
for (const msg of copied) {
|
|
76
|
+
// Skip system messages (preserve prompt cache prefix exactly)
|
|
77
|
+
if (msg.role === 'system')
|
|
78
|
+
continue;
|
|
79
|
+
if (!Array.isArray(msg.content))
|
|
80
|
+
continue;
|
|
81
|
+
for (const block of msg.content) {
|
|
82
|
+
// Only process tool_result blocks — this is where HBO data lives
|
|
83
|
+
// (tool_result appears in user messages in Anthropic format)
|
|
84
|
+
if (block.type !== 'tool_result')
|
|
85
|
+
continue;
|
|
86
|
+
const text = typeof block.content === 'string'
|
|
87
|
+
? block.content
|
|
88
|
+
: extractText(block.content);
|
|
89
|
+
if (!text)
|
|
90
|
+
continue;
|
|
91
|
+
const toolId = block.tool_use_id;
|
|
92
|
+
const result = this.router.route(text, toolId, ccrStore);
|
|
93
|
+
if (result.compressed) {
|
|
94
|
+
block.content = result.output;
|
|
95
|
+
totalTokensSaved += result.tokensSaved;
|
|
96
|
+
transformsApplied.add(result.strategy);
|
|
97
|
+
if (result.ccrHash)
|
|
98
|
+
ccrHashes.push(result.ccrHash);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const newLen = originalLength(copied);
|
|
103
|
+
const compressionRatio = origLen > 0 ? newLen / origLen : 1;
|
|
104
|
+
return {
|
|
105
|
+
messages: copied,
|
|
106
|
+
tokensSaved: totalTokensSaved,
|
|
107
|
+
ccrHashes,
|
|
108
|
+
compressionRatio,
|
|
109
|
+
transformsApplied: [...transformsApplied],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.CompressionPipeline = CompressionPipeline;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createCompressionServer = createCompressionServer;
|
|
7
|
+
/**
|
|
8
|
+
* lib/compression/server.ts
|
|
9
|
+
*
|
|
10
|
+
* Helios Compression Server — TypeScript implementation of the Headroom proxy
|
|
11
|
+
* HTTP interface.
|
|
12
|
+
*
|
|
13
|
+
* Replaces the Python headroom proxy entirely. Starts in milliseconds (no ML
|
|
14
|
+
* model loading), works natively on Windows and macOS.
|
|
15
|
+
*
|
|
16
|
+
* Endpoints (headroom proxy compatible):
|
|
17
|
+
* GET /health → { status, version, uptime }
|
|
18
|
+
* GET /stats → compression statistics
|
|
19
|
+
* POST /v1/messages → compress Anthropic message array
|
|
20
|
+
* POST /v1/chat/completions → compress OpenAI message array
|
|
21
|
+
* GET /v1/retrieve/:hash → retrieve original CCR content
|
|
22
|
+
* POST /headroom/compress → direct compress endpoint
|
|
23
|
+
*
|
|
24
|
+
* All endpoints are cross-company — there is no tenant scoping in the
|
|
25
|
+
* compression layer. Compression is a pure function of content shape,
|
|
26
|
+
* not company identity.
|
|
27
|
+
*/
|
|
28
|
+
const http_1 = __importDefault(require("http"));
|
|
29
|
+
const pipeline_js_1 = require("./pipeline.js");
|
|
30
|
+
const ccr_store_js_1 = require("./ccr-store.js");
|
|
31
|
+
const VERSION = '1.0.0-helios';
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
// Singleton pipeline and CCR store
|
|
34
|
+
const pipeline = new pipeline_js_1.CompressionPipeline();
|
|
35
|
+
const ccrStore = (0, ccr_store_js_1.getCcrStore)(7200);
|
|
36
|
+
// Compression telemetry (process-lifetime counters)
|
|
37
|
+
const stats = {
|
|
38
|
+
requests: { total: 0, compressed: 0, passthrough: 0, failed: 0 },
|
|
39
|
+
tokens: { saved: 0 },
|
|
40
|
+
ccrStore: { puts: 0 },
|
|
41
|
+
};
|
|
42
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
43
|
+
function readBody(req) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
req.on('data', (c) => chunks.push(c));
|
|
47
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
48
|
+
req.on('error', reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function json(res, status, body) {
|
|
52
|
+
const payload = JSON.stringify(body);
|
|
53
|
+
res.writeHead(status, {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'Access-Control-Allow-Origin': '*',
|
|
56
|
+
'X-Headroom-Version': VERSION,
|
|
57
|
+
});
|
|
58
|
+
res.end(payload);
|
|
59
|
+
}
|
|
60
|
+
function error(res, status, message) {
|
|
61
|
+
json(res, status, { error: message });
|
|
62
|
+
}
|
|
63
|
+
// ── Route handlers ────────────────────────────────────────────────────────────
|
|
64
|
+
function handleHealth(_req, res) {
|
|
65
|
+
json(res, 200, {
|
|
66
|
+
status: 'healthy',
|
|
67
|
+
version: VERSION,
|
|
68
|
+
uptime: (Date.now() - startedAt) / 1000,
|
|
69
|
+
available: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function handleStats(_req, res) {
|
|
73
|
+
const ccrStats = ccrStore.stats();
|
|
74
|
+
json(res, 200, {
|
|
75
|
+
available: true,
|
|
76
|
+
requests: stats.requests,
|
|
77
|
+
tokens: {
|
|
78
|
+
saved: stats.tokens.saved,
|
|
79
|
+
savingsPercent: stats.requests.total > 0
|
|
80
|
+
? Math.round((stats.tokens.saved / Math.max(stats.tokens.saved + (stats.requests.total * 100), 1)) * 100)
|
|
81
|
+
: 0,
|
|
82
|
+
},
|
|
83
|
+
ccr: {
|
|
84
|
+
items: ccrStats.entries,
|
|
85
|
+
totalOriginalBytes: ccrStats.totalOriginalBytes,
|
|
86
|
+
},
|
|
87
|
+
uptime: (Date.now() - startedAt) / 1000,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function handleCompressMessages(req, res) {
|
|
91
|
+
stats.requests.total++;
|
|
92
|
+
let body;
|
|
93
|
+
try {
|
|
94
|
+
body = JSON.parse(await readBody(req));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
stats.requests.failed++;
|
|
98
|
+
return error(res, 400, 'Invalid JSON body');
|
|
99
|
+
}
|
|
100
|
+
const messages = body.messages;
|
|
101
|
+
if (!Array.isArray(messages)) {
|
|
102
|
+
// Not a messages request — passthrough
|
|
103
|
+
stats.requests.passthrough++;
|
|
104
|
+
json(res, 200, body);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const result = pipeline.compress(messages, ccrStore);
|
|
109
|
+
stats.tokens.saved += result.tokensSaved;
|
|
110
|
+
if (result.ccrHashes.length > 0)
|
|
111
|
+
stats.ccrStore.puts += result.ccrHashes.length;
|
|
112
|
+
if (result.transformsApplied.length > 0) {
|
|
113
|
+
stats.requests.compressed++;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
stats.requests.passthrough++;
|
|
117
|
+
}
|
|
118
|
+
// Return in same shape as input, with compressed messages
|
|
119
|
+
const responseBody = {
|
|
120
|
+
...body,
|
|
121
|
+
messages: result.messages,
|
|
122
|
+
};
|
|
123
|
+
// Attach headroom metadata in response headers
|
|
124
|
+
res.setHeader('X-Headroom-Saved', String(result.tokensSaved));
|
|
125
|
+
res.setHeader('X-Headroom-Ratio', result.compressionRatio.toFixed(3));
|
|
126
|
+
if (result.ccrHashes.length > 0) {
|
|
127
|
+
res.setHeader('X-Headroom-CCR', result.ccrHashes.join(','));
|
|
128
|
+
}
|
|
129
|
+
if (result.transformsApplied.length > 0) {
|
|
130
|
+
res.setHeader('X-Headroom-Transforms', result.transformsApplied.join(','));
|
|
131
|
+
}
|
|
132
|
+
json(res, 200, responseBody);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
stats.requests.failed++;
|
|
136
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
137
|
+
error(res, 500, `Compression failed: ${msg}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function handleDirectCompress(req, res) {
|
|
141
|
+
stats.requests.total++;
|
|
142
|
+
let body;
|
|
143
|
+
try {
|
|
144
|
+
body = JSON.parse(await readBody(req));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
stats.requests.failed++;
|
|
148
|
+
return error(res, 400, 'Invalid JSON body');
|
|
149
|
+
}
|
|
150
|
+
const messages = body.messages;
|
|
151
|
+
if (!Array.isArray(messages)) {
|
|
152
|
+
stats.requests.passthrough++;
|
|
153
|
+
json(res, 200, { messages: body, tokensSaved: 0, compressionRatio: 1, transformsApplied: [], ccrHashes: [] });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const result = pipeline.compress(messages, ccrStore);
|
|
158
|
+
stats.tokens.saved += result.tokensSaved;
|
|
159
|
+
if (result.ccrHashes.length > 0)
|
|
160
|
+
stats.ccrStore.puts += result.ccrHashes.length;
|
|
161
|
+
if (result.transformsApplied.length > 0)
|
|
162
|
+
stats.requests.compressed++;
|
|
163
|
+
else
|
|
164
|
+
stats.requests.passthrough++;
|
|
165
|
+
json(res, 200, {
|
|
166
|
+
messages: result.messages,
|
|
167
|
+
tokensSaved: result.tokensSaved,
|
|
168
|
+
compressionRatio: result.compressionRatio,
|
|
169
|
+
transformsApplied: result.transformsApplied,
|
|
170
|
+
ccrHashes: result.ccrHashes,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
stats.requests.failed++;
|
|
175
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
176
|
+
error(res, 500, `Compression failed: ${msg}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function handleRetrieve(_req, res, hash) {
|
|
180
|
+
if (!hash || !/^[a-f0-9]{8,64}$/.test(hash)) {
|
|
181
|
+
return error(res, 400, 'Invalid hash format');
|
|
182
|
+
}
|
|
183
|
+
const data = ccrStore.get(hash);
|
|
184
|
+
if (!data) {
|
|
185
|
+
return error(res, 404, `CCR hash not found or expired: ${hash}`);
|
|
186
|
+
}
|
|
187
|
+
json(res, 200, {
|
|
188
|
+
hash,
|
|
189
|
+
data: JSON.parse(data),
|
|
190
|
+
originalBytes: data.length,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── Server ────────────────────────────────────────────────────────────────────
|
|
194
|
+
function createCompressionServer() {
|
|
195
|
+
return http_1.default.createServer(async (req, res) => {
|
|
196
|
+
const url = req.url ?? '/';
|
|
197
|
+
const method = req.method ?? 'GET';
|
|
198
|
+
// CORS preflight
|
|
199
|
+
if (method === 'OPTIONS') {
|
|
200
|
+
res.writeHead(204, {
|
|
201
|
+
'Access-Control-Allow-Origin': '*',
|
|
202
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
203
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version',
|
|
204
|
+
});
|
|
205
|
+
res.end();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
// Health
|
|
210
|
+
if (url === '/health' || url === '/livez' || url === '/readyz') {
|
|
211
|
+
return handleHealth(req, res);
|
|
212
|
+
}
|
|
213
|
+
// Stats
|
|
214
|
+
if (url === '/stats' || url === '/metrics') {
|
|
215
|
+
return handleStats(req, res);
|
|
216
|
+
}
|
|
217
|
+
// Direct compress (used by helios-agent internals and context-compaction.ts)
|
|
218
|
+
if (url === '/headroom/compress' && method === 'POST') {
|
|
219
|
+
return await handleDirectCompress(req, res);
|
|
220
|
+
}
|
|
221
|
+
// headroom-ai SDK compress endpoint — the SDK calls POST /v1/compress
|
|
222
|
+
// with { messages, model, config } and expects the same shape back.
|
|
223
|
+
// We map this to handleDirectCompress which is identical in behavior.
|
|
224
|
+
if (url === '/v1/compress' && method === 'POST') {
|
|
225
|
+
return await handleDirectCompress(req, res);
|
|
226
|
+
}
|
|
227
|
+
// Anthropic messages endpoint (intercept + compress)
|
|
228
|
+
if (url === '/v1/messages' && method === 'POST') {
|
|
229
|
+
return await handleCompressMessages(req, res);
|
|
230
|
+
}
|
|
231
|
+
// OpenAI chat completions endpoint (intercept + compress)
|
|
232
|
+
if (url === '/v1/chat/completions' && method === 'POST') {
|
|
233
|
+
return await handleCompressMessages(req, res);
|
|
234
|
+
}
|
|
235
|
+
// CCR retrieval
|
|
236
|
+
const ccrMatch = url.match(/^\/v1\/retrieve\/([a-f0-9]{8,64})$/);
|
|
237
|
+
if (ccrMatch && method === 'GET') {
|
|
238
|
+
return handleRetrieve(req, res, ccrMatch[1]);
|
|
239
|
+
}
|
|
240
|
+
// 404
|
|
241
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
242
|
+
res.end(JSON.stringify({ error: `Unknown endpoint: ${method} ${url}` }));
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
246
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
247
|
+
res.end(JSON.stringify({ error: `Server error: ${msg}` }));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// ── Standalone entry point ────────────────────────────────────────────────────
|
|
252
|
+
if (require.main === module) {
|
|
253
|
+
const port = parseInt(process.env.HEADROOM_PORT ?? '8787', 10);
|
|
254
|
+
const server = createCompressionServer();
|
|
255
|
+
server.listen(port, '127.0.0.1', () => {
|
|
256
|
+
process.stdout.write(JSON.stringify({ event: 'ready', port, version: VERSION }) + '\n');
|
|
257
|
+
});
|
|
258
|
+
server.on('error', (err) => {
|
|
259
|
+
if (err.code === 'EADDRINUSE') {
|
|
260
|
+
process.stderr.write(`[compression-server] Port ${port} already in use\n`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
throw err;
|
|
264
|
+
});
|
|
265
|
+
}
|