{"_id":"@benkhz/context-manager","_rev":"5-1d4114a79a54651723e4edbce3af6af0","name":"@benkhz/context-manager","dist-tags":{"latest":"2.1.0"},"versions":{"1.0.0":{"name":"@benkhz/context-manager","version":"1.0.0","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"license":"MIT","_id":"@benkhz/context-manager@1.0.0","maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"homepage":"https://github.com/BenKhz/context-manager#readme","bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"dist":{"shasum":"c7e4048d28416890804f444cf2e0eddf02b13232","tarball":"https://registry.npmjs.org/@benkhz/context-manager/-/context-manager-1.0.0.tgz","fileCount":8,"integrity":"sha512-v5ZL2I4t0PEa0ZeR7Yxy4B9QlHZ2LGv4U1c8749hg0BfLlD2dQKyaU9CZbINIUOqqvVT/xLxHxWJpaIUs7PslQ==","signatures":[{"sig":"MEUCICnkpTue4nKRdzjV2LcfnUxy1TnuvzDrhdGo7hhpFT1IAiEAsuae9yJbKQtRSiUH8NgGJB/zgdqa6oSxZBby7sKoHrU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24382},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","exports":{".":"./src/index.js","./presets/openai":"./src/presets/openai.js","./presets/anthropic":"./src/presets/anthropic.js"},"gitHead":"10749bae8334176248f7155866ed9617ed9f476d","scripts":{"test":"vitest run","build":"echo 'No build step — pure ESM source'","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"benkhz","email":"benbernardy@gmail.com"},"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"_npmVersion":"11.12.1","description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.35","jsdom":"^29.1.1","react":"^19.2.6","vitest":"^3.2.4","react-dom":"^19.2.6","@vitejs/plugin-vue":"^6.0.7","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10","@testing-library/vue":"^8.1.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"vue":">=3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/context-manager_1.0.0_1781539336257_0.40701692453663285","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@benkhz/context-manager","version":"2.0.0","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"license":"MIT","_id":"@benkhz/context-manager@2.0.0","maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"homepage":"https://github.com/BenKhz/context-manager#readme","bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"dist":{"shasum":"2ffa96aa0d95153daf5990f23782ad027503454b","tarball":"https://registry.npmjs.org/@benkhz/context-manager/-/context-manager-2.0.0.tgz","fileCount":8,"integrity":"sha512-BDaA073u8MZQjwVwMDWmbGOijp5PzXDJ4r5SLYlZDRVM4DNaFxwuqP2H4ORDsQJXeCXP/TqYnRn4Q3GNJfdHvQ==","signatures":[{"sig":"MEYCIQC54x9N1e/JN9Uoc8obSZRoiyl9Te9GMu+ctjdo1g6OQAIhAMZNqyXt8iNEzt21hZZgMerz/3eufB1bQfZdRPLvtgJx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28584},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","exports":{".":"./src/index.js","./presets/openai":"./src/presets/openai.js","./presets/anthropic":"./src/presets/anthropic.js"},"gitHead":"2dca95050331fe703699119cedb82fd1f3eefe8b","scripts":{"test":"vitest run","build":"echo 'No build step — pure ESM source'","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"benkhz","email":"benbernardy@gmail.com"},"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"_npmVersion":"11.12.1","description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.35","jsdom":"^29.1.1","react":"^19.2.6","vitest":"^3.2.4","react-dom":"^19.2.6","@vitejs/plugin-vue":"^6.0.7","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10","@testing-library/vue":"^8.1.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"vue":">=3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/context-manager_2.0.0_1782318725883_0.37030859282279294","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@benkhz/context-manager","version":"2.0.1","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"license":"MIT","_id":"@benkhz/context-manager@2.0.1","maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"homepage":"https://github.com/BenKhz/context-manager#readme","bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"dist":{"shasum":"b66cf674ce2b09bc59fb5fa6d3b5858e183daf2d","tarball":"https://registry.npmjs.org/@benkhz/context-manager/-/context-manager-2.0.1.tgz","fileCount":8,"integrity":"sha512-xnpBTa1r5HiZ3Qn++LIjw7hmpd8Plp9JJVX6wPHvi63UB+e+ZHJF6U/3+h9NUBxYZzRAx6mgMQ8IZaf1lCCU9Q==","signatures":[{"sig":"MEYCIQDuGkpqlSML+gz3NucBg0pLGkwkxg5QwHQOcg1JirIbRwIhAJd2JOuWSeUW6OKbwYrgFKh0RcLby9GX6tCV28XtdSUE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29336},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","exports":{".":"./src/index.js","./presets/openai":"./src/presets/openai.js","./presets/anthropic":"./src/presets/anthropic.js"},"gitHead":"ef63d4a095dea5dfce6622aab20d563c2184a5a2","scripts":{"test":"vitest run","build":"echo 'No build step — pure ESM source'","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"benkhz","email":"benbernardy@gmail.com"},"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"_npmVersion":"11.12.1","description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.35","jsdom":"^29.1.1","react":"^19.2.6","vitest":"^3.2.4","react-dom":"^19.2.6","@vitejs/plugin-vue":"^6.0.7","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10","@testing-library/vue":"^8.1.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"vue":">=3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/context-manager_2.0.1_1782405191124_0.4843668010975144","host":"s3://npm-registry-packages-npm-production"}},"2.0.2":{"name":"@benkhz/context-manager","version":"2.0.2","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"license":"MIT","_id":"@benkhz/context-manager@2.0.2","maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"homepage":"https://github.com/BenKhz/context-manager#readme","bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"dist":{"shasum":"257d35c7fc246c90c0e07ff837a707e9ac4f9104","tarball":"https://registry.npmjs.org/@benkhz/context-manager/-/context-manager-2.0.2.tgz","fileCount":8,"integrity":"sha512-3OuhB9I0/98eJNfQi+ocIkCGeZToqMQm1YPk+sXVBZfF1Lj4RCMA2zjr+Qvp3oVXM57VftN7a+tGOQc3HlDa2g==","signatures":[{"sig":"MEQCIFB+ZuNdli9USugzbVDm+o6NpkxucvBX8zZah9YS2+AsAiA4jptSxbcRPdG5pkeWYW6zddzd1rT5J3FSd6RgYVJbFg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29488},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","exports":{".":"./src/index.js","./presets/openai":"./src/presets/openai.js","./presets/anthropic":"./src/presets/anthropic.js"},"gitHead":"99cf9d332b2e9c3f1bb7d6ebfb9d71c4dbf91847","scripts":{"test":"vitest run","build":"echo 'No build step — pure ESM source'","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"benkhz","email":"benbernardy@gmail.com"},"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"_npmVersion":"11.12.1","description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.35","jsdom":"^29.1.1","react":"^19.2.6","vitest":"^3.2.4","react-dom":"^19.2.6","@vitejs/plugin-vue":"^6.0.7","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10","@testing-library/vue":"^8.1.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"vue":">=3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/context-manager_2.0.2_1782406702397_0.7537769903918272","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"_id":"@benkhz/context-manager@2.1.0","bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"dist":{"shasum":"d52c8dbd2c97701e26696746c39687199ca2b653","tarball":"https://registry.npmjs.org/@benkhz/context-manager/-/context-manager-2.1.0.tgz","fileCount":8,"integrity":"sha512-Za1EedWteuE7Xp9uuYJufqtAgWg5h29mfwzLd9hwE5xYtXSkOvPR73h5+40zxQykd7rpTjjdNhNtLD1PdP6crA==","signatures":[{"sig":"MEUCIQDRGzG7vY5EGuUctl+ZKHRyIZzLpKjwy5is9llMEnT6AgIgJjDtU3SGlpyV+MNCUBHmxTEFXzaFBAHhp9YyZbHMf+E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBKPSbPyCvRmFFV+8z2ipGvvIyBCYJDYIL12ZK8NYCCOAiAh+mr9UAuphOsETFcTmDdp5zJ7wxtuPHxx3XJ/9376Uw=="}],"unpackedSize":32306},"main":"./src/index.js","name":"@benkhz/context-manager","type":"module","types":"./src/index.d.ts","exports":{".":"./src/index.js","./presets/openai":"./src/presets/openai.js","./presets/anthropic":"./src/presets/anthropic.js"},"gitHead":"9f0fc4cbb7d1e9e7e27ce312b2b2fb3a3069497a","license":"MIT","scripts":{"test":"vitest run","build":"echo 'No build step — pure ESM source'","test:watch":"vitest","test:coverage":"vitest run --coverage"},"version":"2.1.0","_npmUser":{"name":"benkhz","email":"benbernardy@gmail.com"},"homepage":"https://github.com/BenKhz/context-manager#readme","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"_npmVersion":"11.12.1","description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","directories":{},"maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.35","jsdom":"^29.1.1","react":"^19.2.6","vitest":"^3.2.4","react-dom":"^19.2.6","@vitejs/plugin-vue":"^6.0.7","@vitest/coverage-v8":"^3.2.4","@testing-library/dom":"^10","@testing-library/vue":"^8.1.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"vue":">=3","react":">=18"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/context-manager_2.1.0_1788804706216_0.9394503650415684"}}},"time":{"created":"2026-06-15T16:02:16.103Z","modified":"2026-09-07T18:11:46.465Z","1.0.0":"2026-06-15T16:02:16.392Z","2.0.0":"2026-06-24T16:32:06.065Z","2.0.1":"2026-06-25T16:33:11.289Z","2.0.2":"2026-06-25T16:58:22.603Z","2.1.0":"2026-09-07T18:11:46.298Z"},"bugs":{"url":"https://github.com/BenKhz/context-manager/issues"},"license":"MIT","homepage":"https://github.com/BenKhz/context-manager#readme","keywords":["llm","ai","context-manager","openai","anthropic","tool-calling","agent"],"repository":{"url":"git+https://github.com/BenKhz/context-manager.git","type":"git"},"description":"Provider-agnostic LLM context manager with tool execution, auto-compaction, reactive state, and an event bus.","maintainers":[{"name":"benkhz","email":"benbernardy@gmail.com"}],"readme":"# @benkhz/context-manager\n\nA vanilla JS class that manages LLM conversation context end-to-end. Zero runtime dependencies.\n\n## Installation\n\n```bash\nnpm install @benkhz/context-manager\n```\n\n---\n\n## Quick start\n\n```js\nimport { AIContextManager, openaiPreset } from '@benkhz/context-manager'\n\nconst mgr = new AIContextManager({\n  endpoint: 'https://api.openai.com/v1/chat/completions',\n  model:    'gpt-4o',\n  headers:  { Authorization: `Bearer ${process.env.OPENAI_KEY}` },\n  hooks: {\n    ...openaiPreset,\n  },\n})\n\nconst reply = await mgr.send('What is the capital of France?')\nconsole.log(reply.content) // \"The capital of France is Paris.\"\n```\n\n---\n\n## Design decisions\n\n| Concern | Decision | Rationale |\n|---|---|---|\n| Provider agnosticism | Caller supplies `formatRequest` + `parseResponse` hooks | No lock-in; presets ship for OpenAI and Anthropic as reference |\n| Context compaction | Manager POSTs to the same endpoint with a summarise prompt | Automatic; `onCompact` hook overrides for custom logic |\n| Reactive state | `setState / getState / subscribe` — callbacks fire synchronously | Familiar, zero magic, easy to test |\n| Tool loop cap | 10 iterations max | Guards against infinite loops without blocking legitimate multi-step reasoning |\n| Context sizing | Character count approximation | Token counting requires a tokenizer dep; chars are close enough for limit-triggering |\n\n---\n\n## Constructor\n\n```js\nnew AIContextManager(config)\n```\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `endpoint` | `string` | **required** | URL to POST every LLM request to |\n| `hooks` | `object` | **required** | See [Hooks](#hooks) |\n| `model` | `string` | — | Passed through to `formatRequest` |\n| `maxTokens` | `number` | — | Passed through to `formatRequest` |\n| `contextLimit` | `number` | `80_000` | Char count that triggers auto-compaction |\n| `compactKeepLast` | `number` | `6` | Messages preserved verbatim after compaction |\n| `injectSummary` | `boolean` | `true` | Auto-prepend the latest summary as a `system` message on every LLM request. Set `false` to place it yourself via `context.summary` in `formatRequest`. |\n| `headers` | `object` | `{}` | Extra HTTP headers on every `fetch` call |\n\n---\n\n## Hooks\n\nAll hooks live in `config.hooks`. Only `formatRequest` and `parseResponse` are required.\n\n### Required\n\n#### `formatRequest(context, config) → RequestBody`\n\nConverts the internal context snapshot into the HTTP request body.\n\n```js\n// context shape\n{\n  messages: [{ role, content, toolCalls?, toolCallId? }],\n  tools:    [{ name, schema: { description, parameters } }],\n  summary:  string | null,\n}\n\n// config shape (your constructor options + per-call system override)\n{ endpoint, model, maxTokens, headers, system? }\n```\n\n#### `parseResponse(rawJson) → ParsedResponse`\n\nConverts the raw HTTP response JSON into the internal shape.\n\n```js\n// must return\n{\n  content:    string,                              // assistant text\n  stopReason: string,                              // e.g. 'stop', 'tool_use'\n  toolCalls?: [{ id, name, args }],               // present when model calls tools\n}\n```\n\n### Optional lifecycle hooks\n\n| Hook | Signature | Description |\n|---|---|---|\n| `beforeSend` | `(messages[]) → messages[]` | Transform or filter the message array before each POST. Return the array. |\n| `afterReceive` | `(parsed) → parsed` | Transform the parsed response before tool/message processing. |\n| `onCompact` | `(overflow[], currentSummary) → string \\| void` | Return a summary string to override the LLM-generated one. |\n| `onToolCall` | `(name, args) → args \\| void` | Intercept before a tool runs. Return new args to override. |\n| `onToolResult` | `(name, result) → result` | Transform a tool result before appending to context. |\n| `onError` | `(error, phase) → void` | Called on any error. `phase` is `'send'`, `'compact'`, or `'tool'`. |\n| `onStateChange` | `(key, oldVal, newVal) → void` | Observe every state mutation globally. |\n| `onContextLimit` | `(charCount, limit) → 'compact' \\| 'truncate' \\| 'error'` | Choose what happens when the context limit is hit. Defaults to `'compact'`. |\n\n---\n\n## Messaging API\n\n```js\nconst reply = await mgr.send('your message')\n// → { role: 'assistant', content: string }\n\nawait mgr.send('follow-up', { system: 'You are a pirate.' })\n\nawait mgr.compact()\n```\n\n---\n\n## Tool API\n\n```js\nmgr.addTool(\n  'getWeather',\n  {\n    description: 'Get current weather for a city',\n    parameters: {\n      type: 'object',\n      properties: { city: { type: 'string', description: 'City name' } },\n      required: ['city'],\n    },\n  },\n  async ({ city }) => ({ temp: 72, unit: 'F', condition: 'sunny' })\n)\n\nmgr.removeTool('getWeather')\nmgr.getTools()   // → [{ name, schema }]\n```\n\n---\n\n## Event bus\n\n```js\nmgr.on('message:sent',     ({ message }) => ...)\nmgr.on('message:received', ({ message }) => ...)\nmgr.on('tool:call',        ({ name, args }) => ...)\nmgr.on('tool:result',      ({ name, result }) => ...)\nmgr.on('context:compact',  ({ messageCount }) => ...)\nmgr.on('context:compacted',({ summary }) => ...)\nmgr.on('state:change',     ({ key, oldValue, newValue }) => ...)\nmgr.on('error',            ({ error, phase }) => ...)\n\nmgr.off('message:received', handler)\nmgr.once('message:received', handler)\n```\n\n---\n\n## Reactive state\n\n```js\nmgr.setState('userId', 'u_123')\nmgr.getState('userId')              // → 'u_123'\n\nconst unsub = mgr.subscribe('userId', (newVal, oldVal) => {\n  console.log(`userId changed: ${oldVal} → ${newVal}`)\n})\nunsub()\n```\n\n---\n\n## Introspection\n\n```js\nmgr.getMessages()        // → Message[] — full, never-pruned turn history\nmgr.getActiveMessages()  // → Message[] — current LLM-facing window (post-compaction)\nmgr.getSummary()         // → string | null — latest summary\nmgr.getSummaries()       // → string[] — every summary ever produced, oldest first\nmgr.getTools()           // → [{ name, schema }]\nmgr.getContext()         // → { messages, activeMessages, summary, summaries, tools }\nmgr.loadContext(snap)    // restore state from a getContext()-shaped snapshot — returns this\nmgr.reset()              // clear all message/summary state — returns this\n```\n\n`getMessages()` always returns every turn ever sent or received, even after compaction has shrunk the\nLLM-facing window — useful for rendering a full conversation transcript in a UI. `getActiveMessages()`\nreturns what's actually being sent to the model right now.\n\n`loadContext()` is the write counterpart to `getContext()` — persist its output (sessionStorage, a\ndatabase row, wherever) and hand it back to a fresh instance to resume a conversation without\nreplaying every turn through `send()` again:\n\n```js\nsessionStorage.setItem('chat', JSON.stringify(mgr.getContext()))\n\n// later, in a new instance (e.g. after a page navigation):\nconst saved = sessionStorage.getItem('chat')\nif (saved) mgr.loadContext(JSON.parse(saved))\n```\n\nIt replaces state wholesale, so call it on a freshly constructed instance (or right after `reset()`),\nnot mid-conversation.\n\n---\n\n## Presets\n\n```js\nimport { openaiPreset, anthropicPreset } from '@benkhz/context-manager'\n\n// OpenAI / Azure / Ollama / LM Studio\nconst mgr = new AIContextManager({\n  hooks: {\n    ...openaiPreset,\n    beforeSend: msgs => msgs.filter(m => m.content),\n  },\n})\n\n// Anthropic Messages API\nconst mgr = new AIContextManager({\n  hooks: { ...anthropicPreset },\n})\n```\n\n---\n\n## Context compaction\n\nThe manager tracks two parallel message lists: the full **history** (everything ever sent or\nreceived, exposed via `getMessages()`) and the **active window** (`getActiveMessages()`) — the\nslice actually sent to the LLM, which compaction and truncation shrink. History is never pruned.\n\nThis check runs at the start of every `send()` call, and also between tool-call iterations\n*within* a single turn — a request that triggers several tool calls in a row can grow the active\nwindow past `contextLimit` well before the turn finishes, so compaction can kick in mid-turn\nrather than waiting for the next `send()`.\n\nWhen the character count of the active window exceeds `contextLimit`:\n\n1. `onContextLimit` hook is called — returns `'compact'` (default), `'truncate'`, or `'error'`\n2. If `compact`: the overflow messages are sent to the LLM with a summarise prompt\n3. The summary is stored (and appended to the summary history); the last `compactKeepLast`\n   active messages are kept verbatim\n4. On the next request, the latest summary is auto-prepended as a `system` message ahead of the\n   active window — unless `injectSummary: false`, in which case you place it yourself via\n   `context.summary` in `formatRequest`\n\nThe `onCompact` hook can return a string to bypass the LLM call entirely.\n\nBoth compaction and truncation snap their cut point to avoid splitting a tool-call/tool-result\npair across the boundary — the kept window never starts with an orphaned `tool` message.\n\n---\n\n## Open decisions\n\n- **Token counting** — currently approximated via character count. A future `tokenizer` option could accept a `(messages) => number` fn for more accurate limiting.\n- **Streaming** — `send()` is request/response only.\n- **Persistence** — `getContext()` returns a serialisable snapshot and `loadContext(snapshot)` restores it, completing the persistence story.\n- **Multi-modal** — content is currently assumed to be `string`.\n","readmeFilename":"README.md"}