@code-yeongyu/senpi 2026.7.9 → 2026.7.10-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -1
- package/README.md +2 -2
- package/dist/cli/args.js +3 -3
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +4 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/core/agent-session.d.ts +18 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +153 -36
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/cache-stats.d.ts +49 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +101 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +1 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +54 -79
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/index.js.map +1 -1
- package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
- package/dist/core/extensions/builtin/import-repro.js +100 -6
- package/dist/core/extensions/builtin/import-repro.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.js +15 -0
- package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +3 -1
- package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +4 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +30 -24
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +25 -10
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -6
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +37 -9
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +68 -26
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +6 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +9 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +11 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +10 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +34 -3
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +272 -24
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +5 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +16 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +11 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +1 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +10 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +261 -37
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +3 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +11 -4
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +6 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -3
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/package-manager-cli.d.ts +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +64 -17
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/custom-provider.md +4 -3
- package/docs/extensions.md +34 -10
- package/docs/models.md +49 -8
- package/docs/packages.md +7 -10
- package/docs/rpc.md +15 -5
- package/docs/sdk.md +23 -1
- package/docs/settings.md +3 -2
- package/docs/themes.md +5 -3
- package/docs/tui.md +1 -1
- package/docs/usage.md +2 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/reload-runtime.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@earendil-works/pi-agent-core/README.md +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +37 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +7 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +18 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +72 -37
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +2 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/README.md +4 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +7 -8
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +3 -6
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +5 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +7 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +7 -5
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +8 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +644 -220
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.js +13 -4
- package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts +62 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +32 -20
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts +11 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +6 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +66 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +54 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts +11 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +6 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts +8 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +9 -8
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +101 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +59 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +115 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +61 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +14 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +9 -7
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +158 -64
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +145 -83
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +88 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +78 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +15 -6
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +17 -9
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +22 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -8
- package/npm-shrinkwrap.json +0 -3479
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { SessionEntry } from "./session-manager.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Prompt-cache TTL: idle gaps longer than this are worth mentioning as the
|
|
5
|
+
* likely cause of a miss. Anthropic's default cache TTL is 5 minutes.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CACHE_TTL_MS: number;
|
|
8
|
+
/** A counted cache miss on a single assistant message. */
|
|
9
|
+
export interface CacheMiss {
|
|
10
|
+
/** Prompt tokens that were in the previous turn's prompt but not read from cache. */
|
|
11
|
+
missedTokens: number;
|
|
12
|
+
/** Extra dollars paid vs. a full cache hit; 0 when pricing is unknown. */
|
|
13
|
+
missedCost: number;
|
|
14
|
+
/** Milliseconds since the previous request (which last refreshed the cache). */
|
|
15
|
+
idleMs: number;
|
|
16
|
+
/** True when the model changed relative to the previous request. */
|
|
17
|
+
modelChanged: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CacheWasteTotals {
|
|
20
|
+
missedTokens: number;
|
|
21
|
+
missedCost: number;
|
|
22
|
+
/** Number of counted misses (turns above the noise floor). */
|
|
23
|
+
missCount: number;
|
|
24
|
+
}
|
|
25
|
+
/** Minimal pricing lookup, satisfied by ModelRegistry. Cost is $/million tokens. */
|
|
26
|
+
export interface ModelPriceSource {
|
|
27
|
+
find(provider: string, modelId: string): {
|
|
28
|
+
cost: {
|
|
29
|
+
cacheRead: number;
|
|
30
|
+
};
|
|
31
|
+
} | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Cumulative cache waste across a session: prompt tokens that should have been
|
|
35
|
+
* cache reads (they were in the previous turn's prompt) but were re-billed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function computeCacheWaste(entries: SessionEntry[], models: ModelPriceSource): CacheWasteTotals;
|
|
38
|
+
/**
|
|
39
|
+
* All counted cache misses across a session, keyed by the assistant message
|
|
40
|
+
* (by reference) that paid for them. Used to re-derive transcript notices when
|
|
41
|
+
* rebuilding the chat from entries (resume, post-compaction rebuild).
|
|
42
|
+
*/
|
|
43
|
+
export declare function collectCacheMisses(entries: SessionEntry[], models: ModelPriceSource): Map<AssistantMessage, CacheMiss>;
|
|
44
|
+
/**
|
|
45
|
+
* Detect a cache miss on a just-completed assistant message.
|
|
46
|
+
* `entries` must not yet contain `message` (message_end fires before persistence).
|
|
47
|
+
*/
|
|
48
|
+
export declare function detectCacheMiss(entries: SessionEntry[], message: AssistantMessage, models: ModelPriceSource): CacheMiss | undefined;
|
|
49
|
+
//# sourceMappingURL=cache-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-stats.d.ts","sourceRoot":"","sources":["../../src/core/cache-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAgB,CAAC;AAK1C,0DAA0D;AAC1D,MAAM,WAAW,SAAS;IACzB,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,YAAY,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAAG,SAAS,CAAC;CACrF;AAmGD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAErG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,MAAM,EAAE,gBAAgB,GACtB,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAElC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,gBAAgB,GACtB,SAAS,GAAG,SAAS,CAEvB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt-cache TTL: idle gaps longer than this are worth mentioning as the
|
|
3
|
+
* likely cause of a miss. Anthropic's default cache TTL is 5 minutes.
|
|
4
|
+
*/
|
|
5
|
+
export const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
6
|
+
/** Per-turn misses at or below this are cache breakpoint granularity noise. */
|
|
7
|
+
const NOISE_FLOOR_TOKENS = 1024;
|
|
8
|
+
/**
|
|
9
|
+
* Compute the cache miss for one assistant message relative to the previous
|
|
10
|
+
* request. Returns undefined when nothing is counted: first turn, after a
|
|
11
|
+
* reset, no cache activity ever reported (provider without cache support), or
|
|
12
|
+
* miss below the noise floor.
|
|
13
|
+
*/
|
|
14
|
+
function detectMiss(prev, message, models) {
|
|
15
|
+
const usage = message.usage;
|
|
16
|
+
const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
17
|
+
// A zero-cache turn only counts when cache activity was reported before:
|
|
18
|
+
// on cache-read-only providers that is a total miss, while on providers
|
|
19
|
+
// that never report caching it means nothing.
|
|
20
|
+
if (!prev || promptTokens <= 0 || (usage.cacheRead + usage.cacheWrite === 0 && !prev.reportedCache)) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const missedTokens = Math.min(prev.promptTokens, promptTokens) - usage.cacheRead;
|
|
24
|
+
if (missedTokens <= NOISE_FLOOR_TOKENS)
|
|
25
|
+
return undefined;
|
|
26
|
+
// Extra cost = missed tokens billed at the actual paid rate (input/cacheWrite,
|
|
27
|
+
// incl. write premium) instead of the cache-read rate. Missed tokens can only
|
|
28
|
+
// land in the input or cacheWrite buckets, so the paid rate comes straight
|
|
29
|
+
// from this message's own cost breakdown.
|
|
30
|
+
const paidTokens = usage.input + usage.cacheWrite;
|
|
31
|
+
const paidPerToken = paidTokens > 0 ? (usage.cost.input + usage.cost.cacheWrite) / paidTokens : 0;
|
|
32
|
+
const readPerToken = usage.cacheRead > 0
|
|
33
|
+
? usage.cost.cacheRead / usage.cacheRead
|
|
34
|
+
: (models.find(message.provider, message.model)?.cost.cacheRead ?? 0) / 1_000_000;
|
|
35
|
+
return {
|
|
36
|
+
missedTokens,
|
|
37
|
+
missedCost: missedTokens * Math.max(0, paidPerToken - readPerToken),
|
|
38
|
+
idleMs: Math.max(0, message.timestamp - prev.timestamp),
|
|
39
|
+
modelChanged: `${message.provider}/${message.model}` !== prev.modelKey,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function asPreviousRequest(message, reportedCache) {
|
|
43
|
+
const usage = message.usage;
|
|
44
|
+
const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
45
|
+
if (promptTokens <= 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
promptTokens,
|
|
49
|
+
modelKey: `${message.provider}/${message.model}`,
|
|
50
|
+
timestamp: message.timestamp,
|
|
51
|
+
reportedCache: reportedCache || usage.cacheRead + usage.cacheWrite > 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function scan(entries, models) {
|
|
55
|
+
let prev;
|
|
56
|
+
const totals = { missedTokens: 0, missedCost: 0, missCount: 0 };
|
|
57
|
+
const misses = new Map();
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
60
|
+
// The context legitimately changed; the next turn's prompt is new content,
|
|
61
|
+
// not re-billed content. Model switches are NOT exempt: they re-bill the
|
|
62
|
+
// full prompt and should be counted.
|
|
63
|
+
prev = undefined;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
67
|
+
const miss = detectMiss(prev, entry.message, models);
|
|
68
|
+
if (miss) {
|
|
69
|
+
totals.missedTokens += miss.missedTokens;
|
|
70
|
+
totals.missedCost += miss.missedCost;
|
|
71
|
+
totals.missCount += 1;
|
|
72
|
+
misses.set(entry.message, miss);
|
|
73
|
+
}
|
|
74
|
+
prev = asPreviousRequest(entry.message, prev?.reportedCache ?? false) ?? prev;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { prev, totals, misses };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Cumulative cache waste across a session: prompt tokens that should have been
|
|
81
|
+
* cache reads (they were in the previous turn's prompt) but were re-billed.
|
|
82
|
+
*/
|
|
83
|
+
export function computeCacheWaste(entries, models) {
|
|
84
|
+
return scan(entries, models).totals;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* All counted cache misses across a session, keyed by the assistant message
|
|
88
|
+
* (by reference) that paid for them. Used to re-derive transcript notices when
|
|
89
|
+
* rebuilding the chat from entries (resume, post-compaction rebuild).
|
|
90
|
+
*/
|
|
91
|
+
export function collectCacheMisses(entries, models) {
|
|
92
|
+
return scan(entries, models).misses;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect a cache miss on a just-completed assistant message.
|
|
96
|
+
* `entries` must not yet contain `message` (message_end fires before persistence).
|
|
97
|
+
*/
|
|
98
|
+
export function detectCacheMiss(entries, message, models) {
|
|
99
|
+
return detectMiss(scan(entries, models).prev, message, models);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=cache-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-stats.js","sourceRoot":"","sources":["../../src/core/cache-stats.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport type { SessionEntry } from \"./session-manager.ts\";\n\n/**\n * Prompt-cache TTL: idle gaps longer than this are worth mentioning as the\n * likely cause of a miss. Anthropic's default cache TTL is 5 minutes.\n */\nexport const CACHE_TTL_MS = 5 * 60 * 1000;\n\n/** Per-turn misses at or below this are cache breakpoint granularity noise. */\nconst NOISE_FLOOR_TOKENS = 1024;\n\n/** A counted cache miss on a single assistant message. */\nexport interface CacheMiss {\n\t/** Prompt tokens that were in the previous turn's prompt but not read from cache. */\n\tmissedTokens: number;\n\t/** Extra dollars paid vs. a full cache hit; 0 when pricing is unknown. */\n\tmissedCost: number;\n\t/** Milliseconds since the previous request (which last refreshed the cache). */\n\tidleMs: number;\n\t/** True when the model changed relative to the previous request. */\n\tmodelChanged: boolean;\n}\n\nexport interface CacheWasteTotals {\n\tmissedTokens: number;\n\tmissedCost: number;\n\t/** Number of counted misses (turns above the noise floor). */\n\tmissCount: number;\n}\n\n/** Minimal pricing lookup, satisfied by ModelRegistry. Cost is $/million tokens. */\nexport interface ModelPriceSource {\n\tfind(provider: string, modelId: string): { cost: { cacheRead: number } } | undefined;\n}\n\n/** The last request seen by the scan; everything in its prompt should be cached. */\ninterface PreviousRequest {\n\tpromptTokens: number;\n\tmodelKey: string;\n\ttimestamp: number;\n\t/**\n\t * Sticky: some earlier request in this scan segment reported cache activity.\n\t * Distinguishes a total miss on a cache-read-only provider (OpenAI-style,\n\t * writes unreported) from a provider that never reports caching at all.\n\t */\n\treportedCache: boolean;\n}\n\n/**\n * Compute the cache miss for one assistant message relative to the previous\n * request. Returns undefined when nothing is counted: first turn, after a\n * reset, no cache activity ever reported (provider without cache support), or\n * miss below the noise floor.\n */\nfunction detectMiss(\n\tprev: PreviousRequest | undefined,\n\tmessage: AssistantMessage,\n\tmodels: ModelPriceSource,\n): CacheMiss | undefined {\n\tconst usage = message.usage;\n\tconst promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;\n\t// A zero-cache turn only counts when cache activity was reported before:\n\t// on cache-read-only providers that is a total miss, while on providers\n\t// that never report caching it means nothing.\n\tif (!prev || promptTokens <= 0 || (usage.cacheRead + usage.cacheWrite === 0 && !prev.reportedCache)) {\n\t\treturn undefined;\n\t}\n\n\tconst missedTokens = Math.min(prev.promptTokens, promptTokens) - usage.cacheRead;\n\tif (missedTokens <= NOISE_FLOOR_TOKENS) return undefined;\n\n\t// Extra cost = missed tokens billed at the actual paid rate (input/cacheWrite,\n\t// incl. write premium) instead of the cache-read rate. Missed tokens can only\n\t// land in the input or cacheWrite buckets, so the paid rate comes straight\n\t// from this message's own cost breakdown.\n\tconst paidTokens = usage.input + usage.cacheWrite;\n\tconst paidPerToken = paidTokens > 0 ? (usage.cost.input + usage.cost.cacheWrite) / paidTokens : 0;\n\tconst readPerToken =\n\t\tusage.cacheRead > 0\n\t\t\t? usage.cost.cacheRead / usage.cacheRead\n\t\t\t: (models.find(message.provider, message.model)?.cost.cacheRead ?? 0) / 1_000_000;\n\n\treturn {\n\t\tmissedTokens,\n\t\tmissedCost: missedTokens * Math.max(0, paidPerToken - readPerToken),\n\t\tidleMs: Math.max(0, message.timestamp - prev.timestamp),\n\t\tmodelChanged: `${message.provider}/${message.model}` !== prev.modelKey,\n\t};\n}\n\nfunction asPreviousRequest(message: AssistantMessage, reportedCache: boolean): PreviousRequest | undefined {\n\tconst usage = message.usage;\n\tconst promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;\n\tif (promptTokens <= 0) return undefined;\n\treturn {\n\t\tpromptTokens,\n\t\tmodelKey: `${message.provider}/${message.model}`,\n\t\ttimestamp: message.timestamp,\n\t\treportedCache: reportedCache || usage.cacheRead + usage.cacheWrite > 0,\n\t};\n}\n\nfunction scan(\n\tentries: SessionEntry[],\n\tmodels: ModelPriceSource,\n): { prev: PreviousRequest | undefined; totals: CacheWasteTotals; misses: Map<AssistantMessage, CacheMiss> } {\n\tlet prev: PreviousRequest | undefined;\n\tconst totals: CacheWasteTotals = { missedTokens: 0, missedCost: 0, missCount: 0 };\n\tconst misses = new Map<AssistantMessage, CacheMiss>();\n\n\tfor (const entry of entries) {\n\t\tif (entry.type === \"compaction\" || entry.type === \"branch_summary\") {\n\t\t\t// The context legitimately changed; the next turn's prompt is new content,\n\t\t\t// not re-billed content. Model switches are NOT exempt: they re-bill the\n\t\t\t// full prompt and should be counted.\n\t\t\tprev = undefined;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tconst miss = detectMiss(prev, entry.message, models);\n\t\t\tif (miss) {\n\t\t\t\ttotals.missedTokens += miss.missedTokens;\n\t\t\t\ttotals.missedCost += miss.missedCost;\n\t\t\t\ttotals.missCount += 1;\n\t\t\t\tmisses.set(entry.message, miss);\n\t\t\t}\n\t\t\tprev = asPreviousRequest(entry.message, prev?.reportedCache ?? false) ?? prev;\n\t\t}\n\t}\n\treturn { prev, totals, misses };\n}\n\n/**\n * Cumulative cache waste across a session: prompt tokens that should have been\n * cache reads (they were in the previous turn's prompt) but were re-billed.\n */\nexport function computeCacheWaste(entries: SessionEntry[], models: ModelPriceSource): CacheWasteTotals {\n\treturn scan(entries, models).totals;\n}\n\n/**\n * All counted cache misses across a session, keyed by the assistant message\n * (by reference) that paid for them. Used to re-derive transcript notices when\n * rebuilding the chat from entries (resume, post-compaction rebuild).\n */\nexport function collectCacheMisses(\n\tentries: SessionEntry[],\n\tmodels: ModelPriceSource,\n): Map<AssistantMessage, CacheMiss> {\n\treturn scan(entries, models).misses;\n}\n\n/**\n * Detect a cache miss on a just-completed assistant message.\n * `entries` must not yet contain `message` (message_end fires before persistence).\n */\nexport function detectCacheMiss(\n\tentries: SessionEntry[],\n\tmessage: AssistantMessage,\n\tmodels: ModelPriceSource,\n): CacheMiss | undefined {\n\treturn detectMiss(scan(entries, models).prev, message, models);\n}\n"]}
|
|
@@ -66,9 +66,8 @@ export declare function shouldCompact(contextTokens: number, contextWindow: numb
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function estimateTokens(message: AgentMessage): number;
|
|
68
68
|
/**
|
|
69
|
-
* Find the user message
|
|
69
|
+
* Find the context-visible user-role message that starts the turn containing the given entry index.
|
|
70
70
|
* Returns -1 if no turn start found before the index.
|
|
71
|
-
* BashExecutionMessage is treated like a user message for turn boundaries.
|
|
72
71
|
*/
|
|
73
72
|
export declare function findTurnStartIndex(entries: SessionEntry[], entryIndex: number, startIndex: number): number;
|
|
74
73
|
export interface CutPointResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC3F,OAAO,KAAK,EAA6B,KAAK,EAAuB,KAAK,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/core/compaction/compaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
7
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
8
|
-
import { convertToLlm,
|
|
9
|
-
import { buildSessionContext } from "../session-manager.js";
|
|
8
|
+
import { convertToLlm, filterContextExcludedMessages, isContextExcludedCustomMessage } from "../messages.js";
|
|
9
|
+
import { buildSessionContext, sessionEntryToContextMessages, } from "../session-manager.js";
|
|
10
10
|
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
11
11
|
/**
|
|
12
12
|
* Extract file operations from messages and previous compaction entries.
|
|
@@ -42,32 +42,14 @@ function extractFileOperations(messages, entries, prevCompactionIndex) {
|
|
|
42
42
|
* Extract AgentMessage from an entry if it produces one.
|
|
43
43
|
* Returns undefined for entries that don't contribute to LLM context.
|
|
44
44
|
*/
|
|
45
|
-
function
|
|
46
|
-
if (entry.type === "message") {
|
|
47
|
-
return entry.message;
|
|
48
|
-
}
|
|
49
|
-
if (entry.type === "custom_message") {
|
|
50
|
-
if (isContextExcludedCustomMessage(entry.customType)) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
return createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);
|
|
54
|
-
}
|
|
55
|
-
if (entry.type === "branch_summary") {
|
|
56
|
-
return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
|
|
57
|
-
}
|
|
45
|
+
function contextMessagesForCompactionEntry(entry) {
|
|
58
46
|
if (entry.type === "compaction") {
|
|
59
|
-
return
|
|
47
|
+
return [];
|
|
60
48
|
}
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
function isCutPointCustomMessage(entry) {
|
|
64
|
-
return entry.type === "custom_message" && !isContextExcludedCustomMessage(entry.customType);
|
|
49
|
+
return sessionEntryToContextMessages(entry).filter((message) => message.role !== "custom" || !isContextExcludedCustomMessage(message.customType));
|
|
65
50
|
}
|
|
66
51
|
function getMessageFromEntryForCompaction(entry) {
|
|
67
|
-
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
return getMessageFromEntry(entry);
|
|
52
|
+
return contextMessagesForCompactionEntry(entry)[0];
|
|
71
53
|
}
|
|
72
54
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
73
55
|
enabled: true,
|
|
@@ -229,69 +211,68 @@ export function estimateTokens(message) {
|
|
|
229
211
|
}
|
|
230
212
|
}
|
|
231
213
|
}
|
|
214
|
+
function isCutPointMessage(message) {
|
|
215
|
+
switch (message.role) {
|
|
216
|
+
case "user":
|
|
217
|
+
case "assistant":
|
|
218
|
+
case "bashExecution":
|
|
219
|
+
case "custom":
|
|
220
|
+
case "branchSummary":
|
|
221
|
+
case "compactionSummary":
|
|
222
|
+
return true;
|
|
223
|
+
case "toolResult":
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
function isTurnStartMessage(message) {
|
|
229
|
+
switch (message.role) {
|
|
230
|
+
case "user":
|
|
231
|
+
case "bashExecution":
|
|
232
|
+
case "custom":
|
|
233
|
+
case "branchSummary":
|
|
234
|
+
case "compactionSummary":
|
|
235
|
+
return true;
|
|
236
|
+
case "assistant":
|
|
237
|
+
case "toolResult":
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
function isTurnStartEntry(entry) {
|
|
243
|
+
if (entry.type === "compaction") {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return contextMessagesForCompactionEntry(entry).some(isTurnStartMessage);
|
|
247
|
+
}
|
|
232
248
|
/**
|
|
233
|
-
* Find valid cut points: indices of user
|
|
249
|
+
* Find valid cut points: indices of context-visible user-like or assistant messages.
|
|
234
250
|
* Never cut at tool results (they must follow their tool call).
|
|
235
251
|
* When we cut at an assistant message with tool calls, its tool results follow it
|
|
236
252
|
* and will be kept.
|
|
237
|
-
* BashExecutionMessage is treated like a user message (user-initiated context).
|
|
238
253
|
*/
|
|
239
254
|
function findValidCutPoints(entries, startIndex, endIndex) {
|
|
240
255
|
const cutPoints = [];
|
|
241
256
|
for (let i = startIndex; i < endIndex; i++) {
|
|
242
257
|
const entry = entries[i];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const role = entry.message.role;
|
|
246
|
-
switch (role) {
|
|
247
|
-
case "bashExecution":
|
|
248
|
-
case "custom":
|
|
249
|
-
case "branchSummary":
|
|
250
|
-
case "compactionSummary":
|
|
251
|
-
case "user":
|
|
252
|
-
case "assistant":
|
|
253
|
-
cutPoints.push(i);
|
|
254
|
-
break;
|
|
255
|
-
case "toolResult":
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case "thinking_level_change":
|
|
261
|
-
case "model_change":
|
|
262
|
-
case "compaction":
|
|
263
|
-
case "branch_summary":
|
|
264
|
-
case "custom":
|
|
265
|
-
case "custom_message":
|
|
266
|
-
case "label":
|
|
267
|
-
case "session_info":
|
|
268
|
-
break;
|
|
258
|
+
if (entry.type === "compaction") {
|
|
259
|
+
continue;
|
|
269
260
|
}
|
|
270
|
-
|
|
271
|
-
if (entry.type === "branch_summary" || isCutPointCustomMessage(entry)) {
|
|
261
|
+
if (contextMessagesForCompactionEntry(entry).some(isCutPointMessage)) {
|
|
272
262
|
cutPoints.push(i);
|
|
273
263
|
}
|
|
274
264
|
}
|
|
275
265
|
return cutPoints;
|
|
276
266
|
}
|
|
277
267
|
/**
|
|
278
|
-
* Find the user message
|
|
268
|
+
* Find the context-visible user-role message that starts the turn containing the given entry index.
|
|
279
269
|
* Returns -1 if no turn start found before the index.
|
|
280
|
-
* BashExecutionMessage is treated like a user message for turn boundaries.
|
|
281
270
|
*/
|
|
282
271
|
export function findTurnStartIndex(entries, entryIndex, startIndex) {
|
|
283
272
|
for (let i = entryIndex; i >= startIndex; i--) {
|
|
284
|
-
|
|
285
|
-
// branch_summary and non-excluded custom_message are user-role messages, can start a turn
|
|
286
|
-
if (entry.type === "branch_summary" || isCutPointCustomMessage(entry)) {
|
|
273
|
+
if (isTurnStartEntry(entries[i])) {
|
|
287
274
|
return i;
|
|
288
275
|
}
|
|
289
|
-
if (entry.type === "message") {
|
|
290
|
-
const role = entry.message.role;
|
|
291
|
-
if (role === "user" || role === "bashExecution") {
|
|
292
|
-
return i;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
276
|
}
|
|
296
277
|
return -1;
|
|
297
278
|
}
|
|
@@ -321,10 +302,9 @@ export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
|
321
302
|
let cutIndex = cutPoints[0]; // Default: keep from first message (not header)
|
|
322
303
|
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
323
304
|
const entry = entries[i];
|
|
324
|
-
|
|
305
|
+
const messageTokens = contextMessagesForCompactionEntry(entry).reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
306
|
+
if (messageTokens === 0)
|
|
325
307
|
continue;
|
|
326
|
-
// Estimate this message's size
|
|
327
|
-
const messageTokens = estimateTokens(entry.message);
|
|
328
308
|
accumulatedTokens += messageTokens;
|
|
329
309
|
// Check if we've exceeded the budget
|
|
330
310
|
if (accumulatedTokens >= keepRecentTokens) {
|
|
@@ -343,31 +323,26 @@ export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
|
343
323
|
break;
|
|
344
324
|
}
|
|
345
325
|
}
|
|
346
|
-
// Scan backwards from cutIndex to include
|
|
326
|
+
// Scan backwards from cutIndex to include adjacent metadata entries that do not affect context.
|
|
347
327
|
while (cutIndex > startIndex) {
|
|
348
328
|
const prevEntry = entries[cutIndex - 1];
|
|
349
|
-
// Stop at
|
|
350
|
-
if (prevEntry.type === "compaction") {
|
|
329
|
+
// Stop at compaction boundaries or context-visible entries.
|
|
330
|
+
if (prevEntry.type === "compaction" || contextMessagesForCompactionEntry(prevEntry).length > 0) {
|
|
351
331
|
break;
|
|
352
332
|
}
|
|
353
333
|
if (prevEntry.type === "custom_message" && isContextExcludedCustomMessage(prevEntry.customType)) {
|
|
354
334
|
break;
|
|
355
335
|
}
|
|
356
|
-
if (prevEntry.type === "message") {
|
|
357
|
-
// Stop if we hit any message
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
// Include this non-message entry (bash, settings change, etc.)
|
|
361
336
|
cutIndex--;
|
|
362
337
|
}
|
|
363
338
|
// Determine if this is a split turn
|
|
364
339
|
const cutEntry = entries[cutIndex];
|
|
365
|
-
const
|
|
366
|
-
const turnStartIndex =
|
|
340
|
+
const startsTurn = isTurnStartEntry(cutEntry);
|
|
341
|
+
const turnStartIndex = startsTurn ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
|
|
367
342
|
return {
|
|
368
343
|
firstKeptEntryIndex: cutIndex,
|
|
369
344
|
turnStartIndex,
|
|
370
|
-
isSplitTurn: !
|
|
345
|
+
isSplitTurn: !startsTurn && turnStartIndex !== -1,
|
|
371
346
|
};
|
|
372
347
|
}
|
|
373
348
|
// ============================================================================
|