open-multi-agent-kit 0.95.2 → 0.96.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 (70) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +15 -0
  3. package/dist/approvals/approval-receipt-store.d.ts.map +1 -1
  4. package/dist/approvals/approval-receipt-store.js +25 -0
  5. package/dist/approvals/approval-receipt-store.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +2 -2
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +41 -17
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/provider-resilience.d.ts +7 -0
  11. package/dist/core/provider-resilience.d.ts.map +1 -1
  12. package/dist/core/provider-resilience.js +11 -0
  13. package/dist/core/provider-resilience.js.map +1 -1
  14. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  15. package/dist/core/subagent-orchestration.js +4 -2
  16. package/dist/core/subagent-orchestration.js.map +1 -1
  17. package/dist/core/tools/diagnostics.d.ts.map +1 -1
  18. package/dist/core/tools/diagnostics.js +8 -2
  19. package/dist/core/tools/diagnostics.js.map +1 -1
  20. package/dist/guardrails/evidence-protocol.d.ts +10 -0
  21. package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
  22. package/dist/guardrails/evidence-protocol.js +34 -0
  23. package/dist/guardrails/evidence-protocol.js.map +1 -0
  24. package/dist/guardrails/evidence-system.d.ts +3 -1
  25. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  26. package/dist/guardrails/evidence-system.js +22 -14
  27. package/dist/guardrails/evidence-system.js.map +1 -1
  28. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
  29. package/dist/guardrails/replay-ledger-lock-owner.js +3 -1
  30. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
  31. package/dist/guardrails/replay-payload-hash.d.ts +6 -0
  32. package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
  33. package/dist/guardrails/replay-payload-hash.js +24 -0
  34. package/dist/guardrails/replay-payload-hash.js.map +1 -0
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/types/evidence.d.ts +11 -3
  40. package/dist/types/evidence.d.ts.map +1 -1
  41. package/dist/types/evidence.js.map +1 -1
  42. package/docs/book-to-skill.md +79 -0
  43. package/docs/compaction.md +10 -0
  44. package/docs/docs.json +8 -0
  45. package/docs/index.md +3 -0
  46. package/docs/models.md +12 -0
  47. package/docs/provider-resilience.md +37 -56
  48. package/docs/providers.md +4 -0
  49. package/docs/run-protocol.md +72 -0
  50. package/docs/sdk.md +15 -2
  51. package/docs/sessions.md +6 -0
  52. package/docs/settings.md +2 -0
  53. package/docs/skills.md +9 -1
  54. package/docs/usage.md +1 -1
  55. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  56. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  57. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  58. package/examples/extensions/gondolin/package-lock.json +2 -2
  59. package/examples/extensions/gondolin/package.json +1 -1
  60. package/examples/extensions/sandbox/package-lock.json +2 -2
  61. package/examples/extensions/sandbox/package.json +1 -1
  62. package/examples/extensions/with-deps/package-lock.json +2 -2
  63. package/examples/extensions/with-deps/package.json +1 -1
  64. package/npm-shrinkwrap.json +37 -15
  65. package/package.json +8 -5
  66. package/dist/core/context-budget-compressors.d.ts +0 -50
  67. package/dist/core/context-budget-compressors.d.ts.map +0 -1
  68. package/dist/core/context-budget-compressors.js +0 -166
  69. package/dist/core/context-budget-compressors.js.map +0 -1
  70. package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +0 -553
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "open-multi-agent-kit",
3
- "version": "0.95.2",
3
+ "version": "0.96.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-multi-agent-kit",
9
- "version": "0.95.2",
9
+ "version": "0.96.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "omk-agent-core": "^0.95.2",
13
- "omk-ai": "^0.95.2",
14
- "omk-tui": "^0.95.2",
12
+ "omk-agent-core": "^0.96.0",
13
+ "omk-ai": "^0.96.0",
14
+ "omk-tui": "^0.96.0",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
+ "canonicalize": "4.0.0",
16
17
  "chalk": "5.6.2",
17
18
  "cross-spawn": "7.0.6",
18
19
  "diff": "8.0.4",
@@ -26,7 +27,8 @@
26
27
  "typebox": "1.3.11",
27
28
  "undici": "8.10.0",
28
29
  "yaml": "2.9.0",
29
- "omk-adaptorch-wpl": "^0.95.2"
30
+ "omk-adaptorch-wpl": "^0.96.0",
31
+ "omk-protocol": "^0.96.0"
30
32
  },
31
33
  "optionalDependencies": {
32
34
  "@mariozechner/clipboard": "0.3.9"
@@ -861,6 +863,18 @@
861
863
  "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
862
864
  "license": "BSD-3-Clause"
863
865
  },
866
+ "node_modules/canonicalize": {
867
+ "version": "4.0.0",
868
+ "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-4.0.0.tgz",
869
+ "integrity": "sha512-FEdXzwWs+N3rZqEqpqleiY9M1A6IAf9oo1zHFABnLW9FcJ/jzsu+G/Ks3Hq3FglmPKe80GeGBw8ZXLEnwPB0vQ==",
870
+ "license": "Apache-2.0",
871
+ "bin": {
872
+ "canonicalize": "bin/canonicalize.js"
873
+ },
874
+ "engines": {
875
+ "node": ">=18"
876
+ }
877
+ },
864
878
  "node_modules/chalk": {
865
879
  "version": "5.6.2",
866
880
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
@@ -1271,19 +1285,19 @@
1271
1285
  }
1272
1286
  },
1273
1287
  "node_modules/omk-adaptorch-wpl": {
1274
- "version": "0.95.2",
1275
- "resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.95.2.tgz",
1288
+ "version": "0.96.0",
1289
+ "resolved": "https://registry.npmjs.org/omk-adaptorch-wpl/-/omk-adaptorch-wpl-0.96.0.tgz",
1276
1290
  "license": "MIT",
1277
1291
  "engines": {
1278
1292
  "node": ">=22.19.0"
1279
1293
  }
1280
1294
  },
1281
1295
  "node_modules/omk-agent-core": {
1282
- "version": "0.95.2",
1283
- "resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.95.2.tgz",
1296
+ "version": "0.96.0",
1297
+ "resolved": "https://registry.npmjs.org/omk-agent-core/-/omk-agent-core-0.96.0.tgz",
1284
1298
  "license": "MIT",
1285
1299
  "dependencies": {
1286
- "omk-ai": "^0.95.2",
1300
+ "omk-ai": "^0.96.0",
1287
1301
  "ignore": "7.0.6",
1288
1302
  "typebox": "1.3.11",
1289
1303
  "yaml": "2.9.0"
@@ -1293,8 +1307,8 @@
1293
1307
  }
1294
1308
  },
1295
1309
  "node_modules/omk-ai": {
1296
- "version": "0.95.2",
1297
- "resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.95.2.tgz",
1310
+ "version": "0.96.0",
1311
+ "resolved": "https://registry.npmjs.org/omk-ai/-/omk-ai-0.96.0.tgz",
1298
1312
  "license": "MIT",
1299
1313
  "dependencies": {
1300
1314
  "@anthropic-ai/sdk": "0.91.1",
@@ -1315,9 +1329,17 @@
1315
1329
  "node": ">=22.19.0"
1316
1330
  }
1317
1331
  },
1332
+ "node_modules/omk-protocol": {
1333
+ "version": "0.96.0",
1334
+ "resolved": "https://registry.npmjs.org/omk-protocol/-/omk-protocol-0.96.0.tgz",
1335
+ "license": "MIT",
1336
+ "engines": {
1337
+ "node": ">=22.19.0"
1338
+ }
1339
+ },
1318
1340
  "node_modules/omk-tui": {
1319
- "version": "0.95.2",
1320
- "resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.95.2.tgz",
1341
+ "version": "0.96.0",
1342
+ "resolved": "https://registry.npmjs.org/omk-tui/-/omk-tui-0.96.0.tgz",
1321
1343
  "license": "MIT",
1322
1344
  "dependencies": {
1323
1345
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-multi-agent-kit",
3
- "version": "0.95.2",
3
+ "version": "0.96.0",
4
4
  "description": "OMK (Open Multi-Agent Kit) coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "omkConfig": {
@@ -44,10 +44,11 @@
44
44
  "postinstall": "node ./scripts/link-workspace-deps.mjs"
45
45
  },
46
46
  "dependencies": {
47
- "omk-agent-core": "^0.95.2",
48
- "omk-ai": "^0.95.2",
49
- "omk-tui": "^0.95.2",
47
+ "omk-agent-core": "^0.96.0",
48
+ "omk-ai": "^0.96.0",
49
+ "omk-tui": "^0.96.0",
50
50
  "@silvia-odwyer/photon-node": "0.3.4",
51
+ "canonicalize": "4.0.0",
51
52
  "chalk": "5.6.2",
52
53
  "cross-spawn": "7.0.6",
53
54
  "diff": "8.0.4",
@@ -61,7 +62,8 @@
61
62
  "typebox": "1.3.11",
62
63
  "undici": "8.10.0",
63
64
  "yaml": "2.9.0",
64
- "omk-adaptorch-wpl": "^0.95.2"
65
+ "omk-adaptorch-wpl": "^0.96.0",
66
+ "omk-protocol": "^0.96.0"
65
67
  },
66
68
  "overrides": {
67
69
  "rimraf": "6.1.2",
@@ -79,6 +81,7 @@
79
81
  "@types/ms": "2.1.0",
80
82
  "@types/node": "24.12.4",
81
83
  "@types/proper-lockfile": "4.1.4",
84
+ "fast-check": "4.9.0",
82
85
  "shx": "0.4.0",
83
86
  "typescript": "5.9.3",
84
87
  "vitest": "3.2.6"
@@ -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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EACN,0BAA0B,GAG1B,MAAM,mCAAmC,CAAC;AAqD3C,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAEzC,MAAM,UAAU,uBAAuB,GAAkB;IACxD,OAAO;QACN,GAAG,CAAC,OAAO,EAAE;YACZ,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC7F,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,MAAM,GAAG,EAAE,CAAC;gBAChB,IAAI,OAAO,GAAG,KAAK,CAAC;gBACpB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;oBAChC,OAAO,GAAG,IAAI,CAAC;oBACf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACtB,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBAAA,CAC5D,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;gBACtB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBACjC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC;oBAC1C,MAAM,IAAI,KAAK,CAAC;gBAAA,CAChB,CAAC,CAAC;gBACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC;oBAC1C,MAAM,IAAI,KAAK,CAAC;gBAAA,CAChB,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;oBAC5B,IAAI,OAAO;wBAAE,OAAO;oBACpB,OAAO,GAAG,IAAI,CAAC;oBACf,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gBAAA,CAC5E,CAAC,CAAC;gBACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;oBAC/B,IAAI,OAAO;wBAAE,OAAO;oBACpB,OAAO,GAAG,IAAI,CAAC;oBACf,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gBAAA,CACvD,CAAC,CAAC;gBACH,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAAA,CACvC,CAAC,CAAC;QAAA,CACH;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,2BAA2B,CAAC,OAAO,GAAyB,EAAE,EAA4B;IACzG,OAAO,mBAAmB,CAAC;QAC1B,EAAE,EAAE,cAAc;QAClB,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,UAAU;QACtC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC;QAC5G,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,YAAY,EAAE,OAAO,CAAC,YAAY;KAClC,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,4BAA4B,CAAC,OAAO,GAAyB,EAAE,EAA4B;IAC1G,OAAO,mBAAmB,CAAC;QAC1B,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,WAAW;QACvC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC;QAC5G,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,YAAY,EAAE,OAAO,CAAC,YAAY;KAClC,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,8BAA8B,CAC7C,IAAiC,EACjC,OAAO,GAAyB,EAAE,EACK;IACvC,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACpB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACzB,OAAO,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QAC1B,OAAO,4BAA4B,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,wBAAwB,CAAC,CAAC,2BAA2B,CAAC,OAAO,CAAC,EAAE,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAAA,CAC/G;AAED,MAAM,UAAU,wBAAwB,CAAC,QAA6C,EAA4B;IACjH,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClG,OAAO;QACN,EAAE,EAAE,6BAA6B;QACjC,QAAQ,EAAE,GAAG;QACb,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;QAC7B,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE;YACvB,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACJ,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;wBACpC,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC;wBACxC,SAAS;oBACV,CAAC;oBACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBAC/C,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1D,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC;oBACtF,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,EAAE,WAAW,OAAO,EAAE,CAAC,CAAC;gBAC/C,CAAC;YACF,CAAC;YACD,OAAO,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,EAAE,EAAE,KAAK,CAAC,CAAC;QAAA,CAC/E;KACD,CAAC;AAAA,CACF;AAWD,SAAS,mBAAmB,CAAC,OAAqC,EAA4B;IAC7F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,uBAAuB,EAAE,CAAC;IAC3D,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,0BAA0B,EAAE,CAAC;IAC1E,OAAO;QACN,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,CAAC,WAAW,GAAG;YACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;gBAC/B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,CAAC,WAAW,CAAC;gBACnB,KAAK,EAAE,EAAE;gBACT,SAAS,EAAE,KAAK;aAChB,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAA,CACjD;QACD,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE;YACvB,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACpE,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;gBAC1C,OAAO,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,wBAAwB,CAAC,CAAC,CAAC;YAClG,CAAC;YACD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;YAC1D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;gBAC/B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;gBAC3B,KAAK,EAAE,OAAO,CAAC,IAAI;gBACnB,SAAS;aACT,CAAC,CAAC;YACH,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACrB,OAAO,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;YACpF,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAC3B,OAAO,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE;oBACvD,GAAG,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,QAAQ,IAAI,MAAM,EAAE;iBACjD,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;YAC1E,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAC1D,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpC,OAAO,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC;YACzF,CAAC;YACD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YACnE,OAAO;gBACN,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,YAAY;gBACpB,SAAS,EAAE,OAAO,CAAC,EAAE;gBACrB,KAAK;gBACL,MAAM;gBACN,KAAK,EAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC;aAC3G,CAAC;QAAA,CACF;KACD,CAAC;AAAA,CACF;AAED,SAAS,yBAAyB,CACjC,OAAkC,EAClC,YAAiC,EACjC,KAAwB,EACG;IAC3B,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO;QACN,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,MAAM;QACd,SAAS,EAAE,MAAM;QACjB,KAAK;QACL,MAAM,EAAE,KAAK;QACb,KAAK;KACL,CAAC;AAAA,CACF","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"]}