knitbrain 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/ccr/store.d.ts +57 -0
  4. package/dist/ccr/store.js +195 -0
  5. package/dist/ccr/store.js.map +1 -0
  6. package/dist/dashboard.d.ts +23 -0
  7. package/dist/dashboard.js +125 -0
  8. package/dist/dashboard.js.map +1 -0
  9. package/dist/engine/agents.d.ts +36 -0
  10. package/dist/engine/agents.js +88 -0
  11. package/dist/engine/agents.js.map +1 -0
  12. package/dist/engine/calibration.d.ts +29 -0
  13. package/dist/engine/calibration.js +72 -0
  14. package/dist/engine/calibration.js.map +1 -0
  15. package/dist/engine/feedback.d.ts +30 -0
  16. package/dist/engine/feedback.js +82 -0
  17. package/dist/engine/feedback.js.map +1 -0
  18. package/dist/engine/knowledge.d.ts +22 -0
  19. package/dist/engine/knowledge.js +154 -0
  20. package/dist/engine/knowledge.js.map +1 -0
  21. package/dist/engine/memory.d.ts +35 -0
  22. package/dist/engine/memory.js +93 -0
  23. package/dist/engine/memory.js.map +1 -0
  24. package/dist/engine/meter.d.ts +40 -0
  25. package/dist/engine/meter.js +61 -0
  26. package/dist/engine/meter.js.map +1 -0
  27. package/dist/engine/skills.d.ts +33 -0
  28. package/dist/engine/skills.js +97 -0
  29. package/dist/engine/skills.js.map +1 -0
  30. package/dist/engine/teams.d.ts +28 -0
  31. package/dist/engine/teams.js +58 -0
  32. package/dist/engine/teams.js.map +1 -0
  33. package/dist/engine/workflow.d.ts +18 -0
  34. package/dist/engine/workflow.js +40 -0
  35. package/dist/engine/workflow.js.map +1 -0
  36. package/dist/hooks/index.d.ts +2 -0
  37. package/dist/hooks/index.js +47 -0
  38. package/dist/hooks/index.js.map +1 -0
  39. package/dist/hooks/pretooluse.d.ts +23 -0
  40. package/dist/hooks/pretooluse.js +38 -0
  41. package/dist/hooks/pretooluse.js.map +1 -0
  42. package/dist/hub/client.d.ts +26 -0
  43. package/dist/hub/client.js +64 -0
  44. package/dist/hub/client.js.map +1 -0
  45. package/dist/hub/server.d.ts +23 -0
  46. package/dist/hub/server.js +85 -0
  47. package/dist/hub/server.js.map +1 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +82 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/mcp/tools.d.ts +40 -0
  52. package/dist/mcp/tools.js +461 -0
  53. package/dist/mcp/tools.js.map +1 -0
  54. package/dist/measure.d.ts +27 -0
  55. package/dist/measure.js +25 -0
  56. package/dist/measure.js.map +1 -0
  57. package/dist/optimizer/ast.d.ts +19 -0
  58. package/dist/optimizer/ast.js +189 -0
  59. package/dist/optimizer/ast.js.map +1 -0
  60. package/dist/optimizer/code.d.ts +10 -0
  61. package/dist/optimizer/code.js +231 -0
  62. package/dist/optimizer/code.js.map +1 -0
  63. package/dist/optimizer/json.d.ts +9 -0
  64. package/dist/optimizer/json.js +68 -0
  65. package/dist/optimizer/json.js.map +1 -0
  66. package/dist/optimizer/router.d.ts +39 -0
  67. package/dist/optimizer/router.js +137 -0
  68. package/dist/optimizer/router.js.map +1 -0
  69. package/dist/optimizer/text.d.ts +21 -0
  70. package/dist/optimizer/text.js +88 -0
  71. package/dist/optimizer/text.js.map +1 -0
  72. package/dist/optimizer/types.d.ts +13 -0
  73. package/dist/optimizer/types.js +2 -0
  74. package/dist/optimizer/types.js.map +1 -0
  75. package/dist/paths.d.ts +20 -0
  76. package/dist/paths.js +44 -0
  77. package/dist/paths.js.map +1 -0
  78. package/dist/platforms.d.ts +51 -0
  79. package/dist/platforms.js +157 -0
  80. package/dist/platforms.js.map +1 -0
  81. package/dist/profile.d.ts +3 -0
  82. package/dist/profile.js +169 -0
  83. package/dist/profile.js.map +1 -0
  84. package/dist/proxy/cache-aligner.d.ts +9 -0
  85. package/dist/proxy/cache-aligner.js +15 -0
  86. package/dist/proxy/cache-aligner.js.map +1 -0
  87. package/dist/proxy/index.d.ts +2 -0
  88. package/dist/proxy/index.js +37 -0
  89. package/dist/proxy/index.js.map +1 -0
  90. package/dist/proxy/optimize-request.d.ts +51 -0
  91. package/dist/proxy/optimize-request.js +111 -0
  92. package/dist/proxy/optimize-request.js.map +1 -0
  93. package/dist/proxy/server.d.ts +31 -0
  94. package/dist/proxy/server.js +104 -0
  95. package/dist/proxy/server.js.map +1 -0
  96. package/dist/server.d.ts +19 -0
  97. package/dist/server.js +54 -0
  98. package/dist/server.js.map +1 -0
  99. package/dist/setup.d.ts +28 -0
  100. package/dist/setup.js +96 -0
  101. package/dist/setup.js.map +1 -0
  102. package/dist/tokenizer.d.ts +23 -0
  103. package/dist/tokenizer.js +25 -0
  104. package/dist/tokenizer.js.map +1 -0
  105. package/dist/version.d.ts +3 -0
  106. package/dist/version.js +4 -0
  107. package/dist/version.js.map +1 -0
  108. package/package.json +66 -0
@@ -0,0 +1,169 @@
1
+ import { createReadStream, mkdtempSync, readdirSync, rmSync, statSync } from "node:fs";
2
+ import { createInterface } from "node:readline";
3
+ import { homedir, tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { sha256, createFileCCRStore } from "./ccr/store.js";
6
+ import { compress } from "./optimizer/router.js";
7
+ import { ensureAst, astReady } from "./optimizer/ast.js";
8
+ import { countTokens } from "./tokenizer.js";
9
+ /**
10
+ * `knitbrain profile` — measure YOUR savings on YOUR transcripts.
11
+ *
12
+ * Scans real host transcripts (Claude Code JSONL), runs every sizable
13
+ * tool_result through the actual optimizer (plus the proxy's cross-turn dedup
14
+ * simulated per session), and reports per-shape and overall savings. This is
15
+ * the reproducibility answer to unverifiable headline claims: the number it
16
+ * prints is what THIS machine's sessions would have saved.
17
+ */
18
+ function dupRatio(lines) {
19
+ const uniq = new Set(lines).size;
20
+ return lines.length === 0 ? 0 : 1 - uniq / lines.length;
21
+ }
22
+ export function classifyShape(t) {
23
+ const head = t.slice(0, 200).trimStart();
24
+ const lines = t.split("\n");
25
+ if (head.startsWith("<system-reminder"))
26
+ return "system-reminder";
27
+ if (head.startsWith("{") || head.startsWith("[")) {
28
+ try {
29
+ JSON.parse(t);
30
+ return "json";
31
+ }
32
+ catch {
33
+ /* fallthrough */
34
+ }
35
+ }
36
+ let numbered = 0;
37
+ for (const l of lines)
38
+ if (/^\s{0,8}\d+→/.test(l))
39
+ numbered += 1;
40
+ if (numbered >= lines.length * 0.6)
41
+ return "numbered-read";
42
+ if (/^diff --git|^@@ |\n@@ /.test(t) || (/^--- /m.test(t) && /^\+\+\+ /m.test(t)))
43
+ return "diff";
44
+ if (/\b(\d+ (passing|passed|failed)|Tests:|Test Files|PASS|FAIL|✓|✗)\b/.test(t) && lines.length > 15)
45
+ return "test-output";
46
+ if (dupRatio(lines) > 0.25 && lines.length >= 20)
47
+ return "repetitive-log";
48
+ if (/[{};]/.test(t) && /\b(function|const|class|import|export|def|return)\b/.test(t))
49
+ return "code";
50
+ if (lines.length > 40)
51
+ return "long-prose";
52
+ return "short-prose";
53
+ }
54
+ function collectTranscripts(roots) {
55
+ const files = [];
56
+ for (const r of roots) {
57
+ let st;
58
+ try {
59
+ st = statSync(r);
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (st.isFile()) {
65
+ files.push(r);
66
+ continue;
67
+ }
68
+ for (const proj of readdirSync(r)) {
69
+ const pd = join(r, proj);
70
+ try {
71
+ if (!statSync(pd).isDirectory())
72
+ continue;
73
+ for (const f of readdirSync(pd))
74
+ if (f.endsWith(".jsonl"))
75
+ files.push(join(pd, f));
76
+ }
77
+ catch {
78
+ /* skip unreadable */
79
+ }
80
+ }
81
+ }
82
+ return files;
83
+ }
84
+ /** Run the profile and print the report. Returns the overall saved percent. */
85
+ export async function runProfile(args, log = console.log) {
86
+ const roots = args.length > 0 ? args : [join(homedir(), ".claude", "projects")];
87
+ const files = collectTranscripts(roots);
88
+ log(`[profile] transcripts: ${files.length}`);
89
+ if (files.length === 0) {
90
+ log("[profile] nothing to scan — pass a directory or .jsonl path (default: ~/.claude/projects)");
91
+ return 0;
92
+ }
93
+ await ensureAst();
94
+ log(`[profile] AST parsers: ${astReady() ? "warm" : "unavailable — scanner fallback"}`);
95
+ const store = mkdtempSync(join(tmpdir(), "knitbrain-profile-"));
96
+ const ccr = createFileCCRStore(store);
97
+ const buckets = new Map();
98
+ let dedupN = 0;
99
+ let dedupSaved = 0;
100
+ try {
101
+ for (const file of files) {
102
+ const seen = new Set(); // per-session: proxy dedup is per request history
103
+ const rl = createInterface({ input: createReadStream(file), crlfDelay: Infinity });
104
+ for await (const line of rl) {
105
+ let msg;
106
+ try {
107
+ msg = JSON.parse(line);
108
+ }
109
+ catch {
110
+ continue;
111
+ }
112
+ const content = msg?.message?.content;
113
+ if (!Array.isArray(content))
114
+ continue;
115
+ for (const block of content) {
116
+ if (block?.type !== "tool_result")
117
+ continue;
118
+ const texts = typeof block.content === "string"
119
+ ? [block.content]
120
+ : Array.isArray(block.content)
121
+ ? block.content
122
+ .filter((c) => c?.type === "text")
123
+ .map((c) => c.text)
124
+ : [];
125
+ for (const t of texts) {
126
+ if (typeof t !== "string" || t.length < 400)
127
+ continue;
128
+ const shape = classifyShape(t);
129
+ const hash = sha256(t);
130
+ const repeat = seen.has(hash);
131
+ seen.add(hash);
132
+ const r = compress(t, ccr);
133
+ let after = r.skeletonTokens;
134
+ if (repeat) {
135
+ const marker = countTokens(`⟪same as earlier ⟨ccr:${hash}⟩⟫`);
136
+ if (marker < after) {
137
+ dedupN += 1;
138
+ dedupSaved += after - marker;
139
+ after = marker;
140
+ }
141
+ }
142
+ const b = buckets.get(shape) ?? { n: 0, before: 0, after: 0 };
143
+ b.n += 1;
144
+ b.before += r.originalTokens;
145
+ b.after += after;
146
+ buckets.set(shape, b);
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ finally {
153
+ rmSync(store, { recursive: true, force: true });
154
+ }
155
+ const rows = [...buckets.entries()].sort((a, b) => b[1].before - a[1].before);
156
+ const totB = rows.reduce((s, [, v]) => s + v.before, 0);
157
+ const totA = rows.reduce((s, [, v]) => s + v.after, 0);
158
+ log("\nshape n tokens %of-burn saved");
159
+ for (const [shape, v] of rows) {
160
+ const saved = v.before ? Math.round((1 - v.after / v.before) * 1000) / 10 : 0;
161
+ log(`${shape.padEnd(15)} ${String(v.n).padStart(5)} ${String(v.before).padStart(10)} ${String(Math.round((v.before / totB) * 100)).padStart(8)}% ${String(saved).padStart(9)}%`);
162
+ }
163
+ log(`\ncross-turn dedup: ${dedupN} repeated blocks, ${dedupSaved} extra tokens saved`);
164
+ const overall = totB === 0 ? 0 : Math.round((1 - totA / totB) * 1000) / 10;
165
+ log(`\nTOTAL ${totB} → ${totA} tokens overall saved=${overall}%`);
166
+ log("(lossless: every elision carries a ⟨ccr:hash⟩ that recovers the exact original)");
167
+ return overall;
168
+ }
169
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * CacheAligner — stabilize the request's stable prefix (the system prompt) so
3
+ * the provider's KV-cache hits across turns. We only do meaning-preserving
4
+ * whitespace normalization (collapse trailing whitespace + blank-line runs);
5
+ * the instruction text itself is never altered.
6
+ */
7
+ export declare function normalizePrefix(prefix: string): string;
8
+ /** Stable content hash of the normalized prefix — for cache-hit metrics. */
9
+ export declare function prefixHash(prefix: string): string;
@@ -0,0 +1,15 @@
1
+ import { sha256 } from "../ccr/store.js";
2
+ /**
3
+ * CacheAligner — stabilize the request's stable prefix (the system prompt) so
4
+ * the provider's KV-cache hits across turns. We only do meaning-preserving
5
+ * whitespace normalization (collapse trailing whitespace + blank-line runs);
6
+ * the instruction text itself is never altered.
7
+ */
8
+ export function normalizePrefix(prefix) {
9
+ return prefix.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
10
+ }
11
+ /** Stable content hash of the normalized prefix — for cache-hit metrics. */
12
+ export function prefixHash(prefix) {
13
+ return sha256(normalizePrefix(prefix));
14
+ }
15
+ //# sourceMappingURL=cache-aligner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-aligner.js","sourceRoot":"","sources":["../../src/proxy/cache-aligner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { createProxyServer } from "./server.js";
3
+ import { createFileCCRStore } from "../ccr/store.js";
4
+ import { createFeedback } from "../engine/feedback.js";
5
+ import { createMeter } from "../engine/meter.js";
6
+ import { ccrRoot, feedbackRoot, meterRoot } from "../paths.js";
7
+ const port = Number(process.env["KNITBRAIN_PROXY_PORT"] ?? 8788);
8
+ // Provider is auto-detected from the request path; upstreams are overridable.
9
+ const override = process.env["KNITBRAIN_UPSTREAM"];
10
+ const upstreams = {
11
+ ...(process.env["KNITBRAIN_UPSTREAM_ANTHROPIC"]
12
+ ? { anthropic: process.env["KNITBRAIN_UPSTREAM_ANTHROPIC"] }
13
+ : {}),
14
+ ...(process.env["KNITBRAIN_UPSTREAM_OPENAI"]
15
+ ? { openai: process.env["KNITBRAIN_UPSTREAM_OPENAI"] }
16
+ : {}),
17
+ };
18
+ const meter = createMeter(meterRoot());
19
+ const server = createProxyServer({
20
+ ccr: createFileCCRStore(ccrRoot()),
21
+ // Shared TOIN store: retrievals voted via MCP back off proxy prose anchoring too.
22
+ feedback: createFeedback(feedbackRoot()),
23
+ ...(override ? { upstream: override } : {}),
24
+ upstreams,
25
+ onStats: (s) => {
26
+ // The optimized request size IS the live context window usage.
27
+ meter.onRequest(s.originalTokens, s.optimizedTokens);
28
+ if (s.blocksCompressed > 0) {
29
+ console.error(`[knitbrain-proxy] ${s.originalTokens}→${s.optimizedTokens} tok (saved ${s.savedPct}%, ${s.blocksCompressed} blocks)`);
30
+ }
31
+ },
32
+ });
33
+ // Local-first: bind loopback only.
34
+ server.listen(port, "127.0.0.1", () => {
35
+ console.error(`[knitbrain-proxy] listening on http://127.0.0.1:${port} — provider auto-detected per request (Anthropic /v1/messages · OpenAI /v1/chat/completions)`);
36
+ });
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,IAAI,CAAC,CAAC;AACjE,8EAA8E;AAC9E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AACnD,MAAM,SAAS,GAAG;IAChB,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC;QAC7C,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,EAAE;QAC5D,CAAC,CAAC,EAAE,CAAC;IACP,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;QAC1C,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,EAAE;QACtD,CAAC,CAAC,EAAE,CAAC;CACR,CAAC;AAEF,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;AAEvC,MAAM,MAAM,GAAG,iBAAiB,CAAC;IAC/B,GAAG,EAAE,kBAAkB,CAAC,OAAO,EAAE,CAAC;IAClC,kFAAkF;IAClF,QAAQ,EAAE,cAAc,CAAC,YAAY,EAAE,CAAC;IACxC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,SAAS;IACT,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QACb,+DAA+D;QAC/D,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;QACrD,IAAI,CAAC,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CACX,qBAAqB,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,eAAe,eAAe,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,gBAAgB,UAAU,CACtH,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,mCAAmC;AACnC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;IACpC,OAAO,CAAC,KAAK,CACX,mDAAmD,IAAI,8FAA8F,CACtJ,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { CCRStore } from "../ccr/store.js";
2
+ import type { ContentType } from "../optimizer/types.js";
3
+ /**
4
+ * Minimal Anthropic-style request shapes. We treat the body as mostly opaque
5
+ * (pass unknown fields through untouched) and only transform message content.
6
+ */
7
+ export interface ContentBlock {
8
+ type: string;
9
+ text?: string;
10
+ [k: string]: unknown;
11
+ }
12
+ export type MessageContent = string | ContentBlock[];
13
+ export interface Message {
14
+ role: string;
15
+ content: MessageContent;
16
+ [k: string]: unknown;
17
+ }
18
+ export interface RequestBody {
19
+ system?: string | ContentBlock[];
20
+ messages: Message[];
21
+ [k: string]: unknown;
22
+ }
23
+ export interface OptimizeOptions {
24
+ /** Recent messages kept fully uncompressed (rolling window). */
25
+ keepLastTurns?: number;
26
+ /** Only compress text blocks longer than this many characters. */
27
+ minBlockChars?: number;
28
+ /** Whether short-prose sentence anchoring may apply (TOIN-gated by callers). */
29
+ allowProse?: boolean;
30
+ }
31
+ export interface ProxyStats {
32
+ originalTokens: number;
33
+ optimizedTokens: number;
34
+ savedPct: number;
35
+ blocksCompressed: number;
36
+ /** Blocks collapsed to a ⟪same as …⟫ marker because identical content appeared earlier in the history. */
37
+ blocksDeduped: number;
38
+ handles: string[];
39
+ /** Content kind per compressed handle — lets callers feed TOIN (onCompress). */
40
+ kinds: Record<string, ContentType>;
41
+ }
42
+ /**
43
+ * Optimize an LLM request: protect the recent turns + the current intent +
44
+ * the system prompt; compress large text blocks in older turns (original
45
+ * preserved in CCR, recoverable via the ⟨ccr:hash⟩ the model sees). The system
46
+ * prefix is whitespace-normalized (meaning-preserving) for KV-cache stability.
47
+ */
48
+ export declare function optimizeRequest(body: RequestBody, ccr: CCRStore, options?: OptimizeOptions): {
49
+ body: RequestBody;
50
+ stats: ProxyStats;
51
+ };
@@ -0,0 +1,111 @@
1
+ import { sha256 } from "../ccr/store.js";
2
+ import { compress } from "../optimizer/router.js";
3
+ import { countTokens } from "../tokenizer.js";
4
+ import { normalizePrefix } from "./cache-aligner.js";
5
+ const DEFAULTS = { keepLastTurns: 2, minBlockChars: 200, allowProse: true };
6
+ /** Concatenate all human-readable text in a request, for honest token accounting. */
7
+ function collectText(body) {
8
+ const parts = [];
9
+ const sys = body.system;
10
+ if (typeof sys === "string")
11
+ parts.push(sys);
12
+ else if (Array.isArray(sys))
13
+ for (const b of sys)
14
+ if (b.text)
15
+ parts.push(b.text);
16
+ for (const m of body.messages) {
17
+ if (typeof m.content === "string")
18
+ parts.push(m.content);
19
+ else
20
+ for (const b of m.content)
21
+ if (b.text)
22
+ parts.push(b.text);
23
+ }
24
+ return parts.join("\n");
25
+ }
26
+ /**
27
+ * Optimize an LLM request: protect the recent turns + the current intent +
28
+ * the system prompt; compress large text blocks in older turns (original
29
+ * preserved in CCR, recoverable via the ⟨ccr:hash⟩ the model sees). The system
30
+ * prefix is whitespace-normalized (meaning-preserving) for KV-cache stability.
31
+ */
32
+ export function optimizeRequest(body, ccr, options = {}) {
33
+ const opts = { ...DEFAULTS, ...options };
34
+ const before = countTokens(collectText(body));
35
+ const handles = [];
36
+ const kinds = {};
37
+ let blocksCompressed = 0;
38
+ let blocksDeduped = 0;
39
+ const keep = (handle, kind) => {
40
+ blocksCompressed += 1;
41
+ handles.push(handle);
42
+ kinds[handle] = kind;
43
+ };
44
+ // CROSS-TURN DEDUP: agents re-send the same bulk repeatedly (re-reading a
45
+ // file, the same tool output pasted twice). The FIRST occurrence keeps its
46
+ // skeleton; identical repeats collapse to a marker pointing at the same CCR
47
+ // original (sha256(text) IS the handle — content-addressed). Lossless.
48
+ const seen = new Set();
49
+ const dedup = (text) => {
50
+ const hash = sha256(text);
51
+ if (!seen.has(hash)) {
52
+ seen.add(hash);
53
+ return null;
54
+ }
55
+ ccr.put(text); // idempotent — guarantees the handle resolves
56
+ blocksDeduped += 1;
57
+ handles.push(hash);
58
+ return `⟪same as earlier ⟨ccr:${hash}⟩⟫`;
59
+ };
60
+ // OLD turns: compress the whole block (it already served its purpose).
61
+ const compressString = (text) => {
62
+ if (text.length < opts.minBlockChars)
63
+ return text;
64
+ const repeat = dedup(text);
65
+ if (repeat !== null)
66
+ return repeat;
67
+ const r = compress(text, ccr, { allowProse: opts.allowProse });
68
+ if (!r.compressed)
69
+ return text;
70
+ keep(r.handle, r.contentType);
71
+ return r.skeleton;
72
+ };
73
+ // PROTECTED turns (incl. current intent): keep the directive verbatim, but
74
+ // compress EMBEDDED BULK — fenced code/data blocks pasted into the message.
75
+ const FENCE = /```([A-Za-z0-9_+.-]*)\n([\s\S]*?)```/g;
76
+ const splitCompress = (text) => text.replace(FENCE, (whole, lang, inner) => {
77
+ if (inner.length < opts.minBlockChars)
78
+ return whole;
79
+ const repeat = dedup(inner);
80
+ if (repeat !== null)
81
+ return "```" + lang + "\n" + repeat + "\n```";
82
+ const r = compress(inner, ccr, { allowProse: opts.allowProse });
83
+ if (!r.compressed)
84
+ return whole;
85
+ keep(r.handle, r.contentType);
86
+ return "```" + lang + "\n" + r.skeleton + "\n```";
87
+ });
88
+ const applyToContent = (content, fn) => {
89
+ if (typeof content === "string")
90
+ return fn(content);
91
+ return content.map((b) => (typeof b.text === "string" ? { ...b, text: fn(b.text) } : b));
92
+ };
93
+ const msgs = body.messages;
94
+ // Rolling window: recent turns are protected (intent verbatim, bulk split);
95
+ // older turns are fully compressed.
96
+ const protectFrom = Math.max(0, msgs.length - opts.keepLastTurns);
97
+ const messages = msgs.map((m, i) => i >= protectFrom
98
+ ? { ...m, content: applyToContent(m.content, splitCompress) }
99
+ : { ...m, content: applyToContent(m.content, compressString) });
100
+ // CacheAligner: whitespace-normalize the system prefix (meaning-preserving).
101
+ let system = body.system;
102
+ if (typeof system === "string")
103
+ system = normalizePrefix(system);
104
+ const optimized = { ...body, messages };
105
+ if (system !== undefined)
106
+ optimized.system = system;
107
+ const after = countTokens(collectText(optimized));
108
+ const savedPct = before === 0 ? 0 : Math.round((1 - after / before) * 1000) / 10;
109
+ return { body: optimized, stats: { originalTokens: before, optimizedTokens: after, savedPct, blocksCompressed, blocksDeduped, handles, kinds } };
110
+ }
111
+ //# sourceMappingURL=optimize-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimize-request.js","sourceRoot":"","sources":["../../src/proxy/optimize-request.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA4CrD,MAAM,QAAQ,GAA8B,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAEvG,qFAAqF;AACrF,SAAS,WAAW,CAAC,IAAiB;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACxB,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SACxC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,KAAK,MAAM,CAAC,IAAI,GAAG;YAAE,IAAI,CAAC,CAAC,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;;YACpD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;gBAAE,IAAI,CAAC,CAAC,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAiB,EACjB,GAAa,EACb,UAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAgC,EAAE,CAAC;IAC9C,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAiB,EAAQ,EAAE;QACvD,gBAAgB,IAAI,CAAC,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACvB,CAAC,CAAC;IAEF,0EAA0E;IAC1E,2EAA2E;IAC3E,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,KAAK,GAAG,CAAC,IAAY,EAAiB,EAAE;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,8CAA8C;QAC7D,aAAa,IAAI,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,yBAAyB,IAAI,IAAI,CAAC;IAC3C,CAAC,CAAC;IAEF,uEAAuE;IACvE,MAAM,cAAc,GAAG,CAAC,IAAY,EAAU,EAAE;QAC9C,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QAClD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;QACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,CAAC,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC;QAC9B,OAAO,CAAC,CAAC,QAAQ,CAAC;IACpB,CAAC,CAAC;IAEF,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,KAAK,GAAG,uCAAuC,CAAC;IACtD,MAAM,aAAa,GAAG,CAAC,IAAY,EAAU,EAAE,CAC7C,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAa,EAAE,IAAY,EAAE,KAAa,EAAE,EAAE;QACjE,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa;YAAE,OAAO,KAAK,CAAC;QACpD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;QACnE,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC;QAC9B,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC;IACpD,CAAC,CAAC,CAAC;IAEL,MAAM,cAAc,GAAG,CACrB,OAAuB,EACvB,EAAyB,EACT,EAAE;QAClB,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;QACpD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC3B,4EAA4E;IAC5E,oCAAoC;IACpC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjC,CAAC,IAAI,WAAW;QACd,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,EAAE;QAC7D,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CACjE,CAAC;IAEF,6EAA6E;IAC7E,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAEjE,MAAM,SAAS,GAAgB,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrD,IAAI,MAAM,KAAK,SAAS;QAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC;IAEpD,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACjF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;AACnJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { type Server } from "node:http";
2
+ import type { CCRStore } from "../ccr/store.js";
3
+ import type { Feedback } from "../engine/feedback.js";
4
+ import { type OptimizeOptions, type ProxyStats } from "./optimize-request.js";
5
+ /** LLM API protocol, detected from the request path (platform-agnostic). */
6
+ export type Provider = "anthropic" | "openai";
7
+ export declare const DEFAULT_UPSTREAMS: Record<Provider, string>;
8
+ /**
9
+ * Detect the provider from the endpoint, so the proxy works no matter which
10
+ * coding platform points at it (Claude Code / Cursor / Codex all speak one of
11
+ * these two protocols). The path is the robust signal.
12
+ */
13
+ export declare function detectProvider(url: string): Provider;
14
+ export interface ProxyConfig {
15
+ ccr: CCRStore;
16
+ /** Force one upstream for every provider (overrides detection). */
17
+ upstream?: string;
18
+ /** Per-provider upstream base URLs (defaults: Anthropic + OpenAI). */
19
+ upstreams?: Partial<Record<Provider, string>>;
20
+ options?: OptimizeOptions;
21
+ /** TOIN feedback store: gates short-prose anchoring per request and records compressions. */
22
+ feedback?: Pick<Feedback, "shouldSkip" | "onCompress">;
23
+ /** Observe optimization stats per request (telemetry hook). */
24
+ onStats?: (stats: ProxyStats) => void;
25
+ }
26
+ /**
27
+ * Build the Lever-B proxy: a loopback HTTP server that compresses the full LLM
28
+ * request (rolling window + structure-preserve + CacheAligner, originals in
29
+ * CCR) and forwards it upstream, streaming the response straight back.
30
+ */
31
+ export declare function createProxyServer(cfg: ProxyConfig): Server;
@@ -0,0 +1,104 @@
1
+ import { createServer } from "node:http";
2
+ import { Readable } from "node:stream";
3
+ import { optimizeRequest } from "./optimize-request.js";
4
+ export const DEFAULT_UPSTREAMS = {
5
+ anthropic: "https://api.anthropic.com",
6
+ openai: "https://api.openai.com",
7
+ };
8
+ /**
9
+ * Detect the provider from the endpoint, so the proxy works no matter which
10
+ * coding platform points at it (Claude Code / Cursor / Codex all speak one of
11
+ * these two protocols). The path is the robust signal.
12
+ */
13
+ export function detectProvider(url) {
14
+ if (url.startsWith("/v1/chat/completions") || url.startsWith("/v1/completions")) {
15
+ return "openai";
16
+ }
17
+ return "anthropic"; // /v1/messages
18
+ }
19
+ /** Resolve the upstream base URL for a request, honoring overrides. */
20
+ function resolveUpstream(cfg, provider) {
21
+ return cfg.upstream ?? cfg.upstreams?.[provider] ?? DEFAULT_UPSTREAMS[provider];
22
+ }
23
+ function readBody(req) {
24
+ return new Promise((resolve, reject) => {
25
+ let data = "";
26
+ req.on("data", (c) => (data += c));
27
+ req.on("end", () => resolve(data));
28
+ req.on("error", reject);
29
+ });
30
+ }
31
+ /** Forward only the auth/version headers the upstream needs. */
32
+ function forwardHeaders(h) {
33
+ const out = { "content-type": "application/json" };
34
+ for (const k of ["x-api-key", "anthropic-version", "anthropic-beta", "authorization"]) {
35
+ const v = h[k];
36
+ if (typeof v === "string")
37
+ out[k] = v;
38
+ }
39
+ return out;
40
+ }
41
+ /**
42
+ * Build the Lever-B proxy: a loopback HTTP server that compresses the full LLM
43
+ * request (rolling window + structure-preserve + CacheAligner, originals in
44
+ * CCR) and forwards it upstream, streaming the response straight back.
45
+ */
46
+ export function createProxyServer(cfg) {
47
+ return createServer((req, res) => {
48
+ void handle(req, res, cfg).catch((err) => {
49
+ if (!res.headersSent)
50
+ res.writeHead(502, { "content-type": "application/json" });
51
+ res.end(JSON.stringify({ error: "knitbrain proxy upstream failure", detail: String(err) }));
52
+ });
53
+ });
54
+ }
55
+ async function handle(req, res, cfg) {
56
+ if (req.method === "GET" && req.url === "/health") {
57
+ res.writeHead(200, { "content-type": "application/json" });
58
+ res.end(JSON.stringify({ status: "healthy", server: "knitbrain-proxy" }));
59
+ return;
60
+ }
61
+ if (req.method === "POST" && req.url && /^\/v1\/(messages|chat\/completions)/.test(req.url)) {
62
+ const raw = await readBody(req);
63
+ let parsed;
64
+ try {
65
+ parsed = JSON.parse(raw);
66
+ }
67
+ catch {
68
+ res.writeHead(400, { "content-type": "application/json" });
69
+ res.end(JSON.stringify({ error: "invalid JSON body" }));
70
+ return;
71
+ }
72
+ const options = { ...cfg.options };
73
+ if (cfg.feedback && options.allowProse === undefined) {
74
+ options.allowProse = !cfg.feedback.shouldSkip("prose");
75
+ }
76
+ const { body, stats } = optimizeRequest(parsed, cfg.ccr, options);
77
+ if (cfg.feedback) {
78
+ for (const [handle, kind] of Object.entries(stats.kinds))
79
+ cfg.feedback.onCompress(kind, handle);
80
+ }
81
+ cfg.onStats?.(stats);
82
+ const provider = detectProvider(req.url);
83
+ const url = resolveUpstream(cfg, provider).replace(/\/+$/, "") + req.url;
84
+ const upstream = await fetch(url, {
85
+ method: "POST",
86
+ headers: forwardHeaders(req.headers),
87
+ body: JSON.stringify(body),
88
+ });
89
+ const headersOut = {
90
+ "content-type": upstream.headers.get("content-type") ?? "application/json",
91
+ };
92
+ res.writeHead(upstream.status, headersOut);
93
+ if (upstream.body) {
94
+ Readable.fromWeb(upstream.body).pipe(res);
95
+ }
96
+ else {
97
+ res.end();
98
+ }
99
+ return;
100
+ }
101
+ res.writeHead(404, { "content-type": "application/json" });
102
+ res.end(JSON.stringify({ error: "not found" }));
103
+ }
104
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { type CCRStore } from "./ccr/store.js";
3
+ import { type Memory } from "./engine/memory.js";
4
+ import { type Knowledge } from "./engine/knowledge.js";
5
+ import { type Feedback } from "./engine/feedback.js";
6
+ import { type TeamBoard } from "./engine/teams.js";
7
+ import { type Meter } from "./engine/meter.js";
8
+ import { type SkillsStore } from "./engine/skills.js";
9
+ import { type Calibration } from "./engine/calibration.js";
10
+ export { VERSION, SERVER_NAME } from "./version.js";
11
+ /**
12
+ * Build the Knit Brain MCP server. Every tool result flows through the ONE
13
+ * dispatch chokepoint, where data outputs are compressed (original preserved
14
+ * in CCR) and governance/verbatim outputs pass through untouched.
15
+ *
16
+ * @param ccr injectable store (tests pass a temp store; default is the
17
+ * local-first store under ~/.knitbrain/ccr).
18
+ */
19
+ export declare function buildServer(ccr?: CCRStore, memory?: Memory, knowledge?: Knowledge, feedback?: Feedback, team?: TeamBoard, meter?: Meter, skills?: SkillsStore, calibration?: Calibration): Server;