bonecode 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +64 -50
- package/bone/output/agent/.dockerignore +7 -7
- package/bone/output/agent/.env.example +36 -36
- package/bone/output/agent/.github/workflows/ci.yaml +58 -58
- package/bone/output/agent/AgentDomain.bone.map +349 -349
- package/bone/output/agent/AgentDomain.postman_collection.json +957 -957
- package/bone/output/agent/Dockerfile +22 -22
- package/bone/output/agent/README.md +47 -47
- package/bone/output/agent/admin/index.html +739 -739
- package/bone/output/agent/docker-compose.yaml +22 -22
- package/bone/output/agent/k8s/deployment.yaml +75 -75
- package/bone/output/agent/migrations/agent.sql +36 -36
- package/bone/output/agent/migrations/agent_instance.sql +36 -36
- package/bone/output/agent/migrations/audit_log.sql +18 -18
- package/bone/output/agent/migrations/build_step.sql +34 -34
- package/bone/output/agent/migrations/event_outbox.sql +31 -31
- package/bone/output/agent/migrations/plan.sql +30 -30
- package/bone/output/agent/migrations/task.sql +30 -30
- package/bone/output/agent/migrations/tool_call.sql +33 -33
- package/bone/output/agent/openapi.yaml +1116 -1116
- package/bone/output/agent/package.json +35 -35
- package/bone/output/agent/schema.graphql +233 -233
- package/bone/output/agent/sdk/client.ts +231 -231
- package/bone/output/agent/src/algorithms.ts +2 -2
- package/bone/output/agent/src/audit.ts +44 -44
- package/bone/output/agent/src/auth.ts +57 -57
- package/bone/output/agent/src/cron.ts +12 -12
- package/bone/output/agent/src/db.ts +31 -31
- package/bone/output/agent/src/debug.ts +66 -66
- package/bone/output/agent/src/events.ts +243 -243
- package/bone/output/agent/src/extensions.ts +54 -54
- package/bone/output/agent/src/failure_rules.ts +322 -322
- package/bone/output/agent/src/flows.ts +168 -168
- package/bone/output/agent/src/health.ts +43 -43
- package/bone/output/agent/src/index.ts +99 -99
- package/bone/output/agent/src/logger.ts +69 -66
- package/bone/output/agent/src/metrics.ts +75 -75
- package/bone/output/agent/src/migrate.ts +351 -351
- package/bone/output/agent/src/migration_diff.ts +108 -108
- package/bone/output/agent/src/notify.ts +125 -125
- package/bone/output/agent/src/routes/plan.ts +91 -91
- package/bone/output/agent/src/routes/task.ts +105 -105
- package/bone/output/agent/src/routes/tool_call.ts +166 -166
- package/bone/output/agent/src/schemas.ts +384 -384
- package/bone/output/agent/src/state_machines/agent_instance.ts +24 -24
- package/bone/output/agent/src/state_machines/build_step.ts +22 -22
- package/bone/output/agent/src/state_machines/plan.ts +22 -22
- package/bone/output/agent/src/state_machines/task.ts +22 -22
- package/bone/output/agent/src/state_machines/tool_call.ts +22 -22
- package/bone/output/agent/src/tests.ts +361 -361
- package/bone/output/agent/src/websocket.ts +200 -200
- package/bone/output/agent/tsconfig.json +24 -24
- package/bone/output/rag/.dockerignore +7 -7
- package/bone/output/rag/.env.example +36 -36
- package/bone/output/rag/.github/workflows/ci.yaml +58 -58
- package/bone/output/rag/Dockerfile +22 -22
- package/bone/output/rag/RAGDomain.bone.map +286 -286
- package/bone/output/rag/RAGDomain.postman_collection.json +922 -922
- package/bone/output/rag/README.md +47 -47
- package/bone/output/rag/admin/index.html +817 -817
- package/bone/output/rag/docker-compose.yaml +22 -22
- package/bone/output/rag/k8s/deployment.yaml +75 -75
- package/bone/output/rag/migrations/audit_log.sql +18 -18
- package/bone/output/rag/migrations/code_chunk.sql +34 -34
- package/bone/output/rag/migrations/code_file.sql +33 -33
- package/bone/output/rag/migrations/event_outbox.sql +31 -31
- package/bone/output/rag/migrations/indexing_job.sql +33 -33
- package/bone/output/rag/migrations/knowledge_base.sql +35 -35
- package/bone/output/rag/migrations/memory_entry.sql +34 -34
- package/bone/output/rag/openapi.yaml +1097 -1097
- package/bone/output/rag/package.json +35 -35
- package/bone/output/rag/schema.graphql +245 -245
- package/bone/output/rag/sdk/client.ts +234 -234
- package/bone/output/rag/src/algorithms.ts +2 -2
- package/bone/output/rag/src/audit.ts +37 -37
- package/bone/output/rag/src/auth.ts +57 -57
- package/bone/output/rag/src/cron.ts +12 -12
- package/bone/output/rag/src/db.ts +31 -31
- package/bone/output/rag/src/debug.ts +66 -66
- package/bone/output/rag/src/events.ts +243 -243
- package/bone/output/rag/src/extensions.ts +350 -350
- package/bone/output/rag/src/failure_rules.ts +314 -314
- package/bone/output/rag/src/flows.ts +239 -239
- package/bone/output/rag/src/health.ts +43 -43
- package/bone/output/rag/src/index.ts +94 -94
- package/bone/output/rag/src/logger.ts +69 -66
- package/bone/output/rag/src/metrics.ts +75 -75
- package/bone/output/rag/src/migrate.ts +363 -363
- package/bone/output/rag/src/migration_diff.ts +108 -108
- package/bone/output/rag/src/notify.ts +99 -99
- package/bone/output/rag/src/routes/code_chunk.ts +75 -75
- package/bone/output/rag/src/routes/code_file.ts +101 -101
- package/bone/output/rag/src/routes/indexing_job.ts +87 -87
- package/bone/output/rag/src/routes/knowledge_base.ts +230 -230
- package/bone/output/rag/src/routes/memory_entry.ts +87 -87
- package/bone/output/rag/src/schemas.ts +394 -394
- package/bone/output/rag/src/state_machines/code_file.ts +23 -23
- package/bone/output/rag/src/state_machines/indexing_job.ts +22 -22
- package/bone/output/rag/src/state_machines/knowledge_base.ts +23 -23
- package/bone/output/rag/src/state_machines/memory_entry.ts +20 -20
- package/bone/output/rag/src/tests.ts +339 -339
- package/bone/output/rag/tsconfig.json +24 -24
- package/bone/output/session/.dockerignore +7 -7
- package/bone/output/session/.env.example +36 -36
- package/bone/output/session/.github/workflows/ci.yaml +58 -58
- package/bone/output/session/Dockerfile +22 -22
- package/bone/output/session/README.md +47 -47
- package/bone/output/session/SessionDomain.bone.map +349 -349
- package/bone/output/session/SessionDomain.postman_collection.json +957 -957
- package/bone/output/session/admin/index.html +666 -666
- package/bone/output/session/docker-compose.yaml +22 -22
- package/bone/output/session/k8s/deployment.yaml +75 -75
- package/bone/output/session/migrations/audit_log.sql +18 -18
- package/bone/output/session/migrations/event_outbox.sql +31 -31
- package/bone/output/session/migrations/message.sql +31 -31
- package/bone/output/session/migrations/part.sql +28 -28
- package/bone/output/session/migrations/permission.sql +28 -28
- package/bone/output/session/migrations/project.sql +28 -28
- package/bone/output/session/migrations/session.sql +38 -38
- package/bone/output/session/openapi.yaml +1101 -1101
- package/bone/output/session/package.json +35 -35
- package/bone/output/session/schema.graphql +222 -222
- package/bone/output/session/sdk/client.ts +225 -225
- package/bone/output/session/src/algorithms.ts +2 -2
- package/bone/output/session/src/audit.ts +44 -44
- package/bone/output/session/src/auth.ts +57 -57
- package/bone/output/session/src/cron.ts +12 -12
- package/bone/output/session/src/db.ts +31 -31
- package/bone/output/session/src/debug.ts +66 -66
- package/bone/output/session/src/events.ts +270 -270
- package/bone/output/session/src/extensions.ts +215 -215
- package/bone/output/session/src/failure_rules.ts +283 -283
- package/bone/output/session/src/flows.ts +168 -168
- package/bone/output/session/src/health.ts +43 -43
- package/bone/output/session/src/index.ts +99 -99
- package/bone/output/session/src/logger.ts +67 -66
- package/bone/output/session/src/metrics.ts +75 -75
- package/bone/output/session/src/migrate.ts +331 -331
- package/bone/output/session/src/migration_diff.ts +108 -108
- package/bone/output/session/src/notify.ts +112 -112
- package/bone/output/session/src/routes/message.ts +93 -93
- package/bone/output/session/src/routes/part.ts +79 -79
- package/bone/output/session/src/routes/permission.ts +79 -79
- package/bone/output/session/src/routes/project.ts +79 -79
- package/bone/output/session/src/routes/session.ts +294 -294
- package/bone/output/session/src/schemas.ts +357 -357
- package/bone/output/session/src/state_machines/session.ts +23 -23
- package/bone/output/session/src/tests.ts +325 -325
- package/bone/output/session/src/websocket.ts +223 -200
- package/bone/output/session/tsconfig.json +24 -24
- package/bone/output/workspace/.dockerignore +7 -7
- package/bone/output/workspace/.env.example +36 -36
- package/bone/output/workspace/.github/workflows/ci.yaml +58 -58
- package/bone/output/workspace/Dockerfile +22 -22
- package/bone/output/workspace/README.md +45 -45
- package/bone/output/workspace/WorkspaceDomain.bone.map +188 -188
- package/bone/output/workspace/WorkspaceDomain.postman_collection.json +620 -620
- package/bone/output/workspace/admin/index.html +484 -484
- package/bone/output/workspace/docker-compose.yaml +22 -22
- package/bone/output/workspace/k8s/deployment.yaml +75 -75
- package/bone/output/workspace/migrations/audit_log.sql +18 -18
- package/bone/output/workspace/migrations/codebase.sql +34 -34
- package/bone/output/workspace/migrations/event_outbox.sql +31 -31
- package/bone/output/workspace/migrations/snapshot.sql +32 -32
- package/bone/output/workspace/migrations/workspace.sql +33 -33
- package/bone/output/workspace/openapi.yaml +721 -721
- package/bone/output/workspace/package.json +35 -35
- package/bone/output/workspace/schema.graphql +153 -153
- package/bone/output/workspace/sdk/client.ts +155 -155
- package/bone/output/workspace/src/algorithms.ts +2 -2
- package/bone/output/workspace/src/audit.ts +37 -37
- package/bone/output/workspace/src/auth.ts +57 -57
- package/bone/output/workspace/src/cron.ts +12 -12
- package/bone/output/workspace/src/db.ts +31 -31
- package/bone/output/workspace/src/debug.ts +66 -66
- package/bone/output/workspace/src/events.ts +243 -243
- package/bone/output/workspace/src/extensions.ts +44 -44
- package/bone/output/workspace/src/failure_rules.ts +152 -152
- package/bone/output/workspace/src/health.ts +43 -43
- package/bone/output/workspace/src/index.ts +88 -88
- package/bone/output/workspace/src/logger.ts +69 -66
- package/bone/output/workspace/src/metrics.ts +75 -75
- package/bone/output/workspace/src/migrate.ts +219 -219
- package/bone/output/workspace/src/migration_diff.ts +108 -108
- package/bone/output/workspace/src/notify.ts +73 -73
- package/bone/output/workspace/src/routes/codebase.ts +87 -87
- package/bone/output/workspace/src/routes/snapshot.ts +127 -127
- package/bone/output/workspace/src/routes/workspace.ts +190 -190
- package/bone/output/workspace/src/schemas.ts +231 -231
- package/bone/output/workspace/src/state_machines/codebase.ts +21 -21
- package/bone/output/workspace/src/state_machines/snapshot.ts +20 -20
- package/bone/output/workspace/src/state_machines/workspace.ts +21 -21
- package/bone/output/workspace/src/tests.ts +248 -248
- package/bone/output/workspace/tsconfig.json +24 -24
- package/compat/opencode_adapter.ts +94 -17
- package/package.json +15 -2
- package/src/cli.ts +66 -107
- package/src/db_adapter.ts +354 -0
- package/src/engine/account/account.sql.ts +39 -39
- package/src/engine/account/account.ts +456 -456
- package/src/engine/account/repo.ts +166 -166
- package/src/engine/account/schema.ts +99 -99
- package/src/engine/account/url.ts +8 -8
- package/src/engine/acp/README.md +174 -174
- package/src/engine/acp/agent.ts +1968 -1968
- package/src/engine/acp/runtime.ts +22 -22
- package/src/engine/acp/session.ts +122 -122
- package/src/engine/acp/types.ts +24 -24
- package/src/engine/agent/agent.ts +463 -463
- package/src/engine/agent/generate.txt +75 -75
- package/src/engine/agent/prompt/compaction.txt +9 -9
- package/src/engine/agent/prompt/explore.txt +18 -18
- package/src/engine/agent/prompt/scout.txt +36 -36
- package/src/engine/agent/prompt/summary.txt +11 -11
- package/src/engine/agent/prompt/title.txt +44 -44
- package/src/engine/agent/subagent-permissions.ts +34 -34
- package/src/engine/auth/index.ts +96 -96
- package/src/engine/background/background/job.ts +200 -200
- package/src/engine/background/job.ts +200 -200
- package/src/engine/bus/bus-event.ts +45 -45
- package/src/engine/bus/global.ts +22 -22
- package/src/engine/bus/index.ts +203 -203
- package/src/engine/command/command/index.ts +181 -181
- package/src/engine/command/command/template/initialize.txt +66 -66
- package/src/engine/command/command/template/review.txt +101 -101
- package/src/engine/command/index.ts +181 -181
- package/src/engine/command/template/initialize.txt +66 -66
- package/src/engine/command/template/review.txt +101 -101
- package/src/engine/config/agent.ts +172 -172
- package/src/engine/config/attachment.ts +25 -25
- package/src/engine/config/command.ts +62 -62
- package/src/engine/config/config.ts +833 -833
- package/src/engine/config/console-state.ts +14 -14
- package/src/engine/config/entry-name.ts +16 -16
- package/src/engine/config/error.ts +23 -23
- package/src/engine/config/formatter.ts +13 -13
- package/src/engine/config/layout.ts +6 -6
- package/src/engine/config/lsp.ts +43 -43
- package/src/engine/config/managed.ts +71 -71
- package/src/engine/config/markdown.ts +96 -96
- package/src/engine/config/mcp.ts +56 -56
- package/src/engine/config/model-id.ts +5 -5
- package/src/engine/config/parse.ts +79 -79
- package/src/engine/config/paths.ts +45 -45
- package/src/engine/config/permission.ts +58 -58
- package/src/engine/config/plugin.ts +84 -84
- package/src/engine/config/provider.ts +111 -111
- package/src/engine/config/reference.ts +23 -23
- package/src/engine/config/server.ts +19 -19
- package/src/engine/config/skills.ts +14 -14
- package/src/engine/config/variable.ts +90 -90
- package/src/engine/control-plane/adapters/index.ts +41 -41
- package/src/engine/control-plane/adapters/worktree.ts +96 -96
- package/src/engine/control-plane/dev/README.md +19 -19
- package/src/engine/control-plane/dev/debug-workspace-plugin.ts +73 -73
- package/src/engine/control-plane/schema.ts +14 -14
- package/src/engine/control-plane/types.ts +59 -59
- package/src/engine/control-plane/util.ts +39 -39
- package/src/engine/control-plane/workspace-adapter-runtime.ts +51 -51
- package/src/engine/control-plane/workspace-context.ts +26 -26
- package/src/engine/control-plane/workspace.sql.ts +20 -20
- package/src/engine/control-plane/workspace.ts +1072 -1072
- package/src/engine/data-migration.ts +161 -161
- package/src/engine/effect/app-runtime.ts +143 -143
- package/src/engine/effect/bootstrap-runtime.ts +29 -29
- package/src/engine/effect/bridge.ts +84 -84
- package/src/engine/effect/config-service.ts +67 -67
- package/src/engine/effect/instance-ref.ts +11 -11
- package/src/engine/effect/instance-registry.ts +12 -12
- package/src/engine/effect/instance-state.ts +72 -72
- package/src/engine/effect/promise.ts +17 -17
- package/src/engine/effect/run-service.ts +47 -47
- package/src/engine/effect/runner.ts +217 -217
- package/src/engine/effect/runtime-flags.ts +74 -74
- package/src/engine/effect/service-use.ts +38 -38
- package/src/engine/env/index.ts +37 -37
- package/src/engine/event-v2-bridge.ts +89 -89
- package/src/engine/file/file/ignore.ts +81 -81
- package/src/engine/file/file/index.ts +651 -651
- package/src/engine/file/file/protected.ts +59 -59
- package/src/engine/file/file/ripgrep.ts +481 -481
- package/src/engine/file/file/watcher.ts +167 -167
- package/src/engine/file/ignore.ts +81 -81
- package/src/engine/file/index.ts +651 -651
- package/src/engine/file/protected.ts +59 -59
- package/src/engine/file/ripgrep.ts +481 -481
- package/src/engine/file/watcher.ts +167 -167
- package/src/engine/format/format/formatter.ts +404 -404
- package/src/engine/format/format/index.ts +209 -209
- package/src/engine/format/formatter.ts +404 -404
- package/src/engine/format/index.ts +209 -209
- package/src/engine/git/git/index.ts +347 -347
- package/src/engine/git/index.ts +347 -347
- package/src/engine/id/id.ts +80 -80
- package/src/engine/ide/index.ts +70 -70
- package/src/engine/image/image/image.ts +176 -176
- package/src/engine/image/image.ts +176 -176
- package/src/engine/index.ts +251 -251
- package/src/engine/installation/index.ts +327 -327
- package/src/engine/lsp/client.ts +707 -707
- package/src/engine/lsp/diagnostic.ts +29 -29
- package/src/engine/lsp/language.ts +121 -121
- package/src/engine/lsp/launch.ts +21 -21
- package/src/engine/lsp/lsp/client.ts +707 -707
- package/src/engine/lsp/lsp/diagnostic.ts +29 -29
- package/src/engine/lsp/lsp/language.ts +121 -121
- package/src/engine/lsp/lsp/launch.ts +21 -21
- package/src/engine/lsp/lsp/lsp.ts +507 -507
- package/src/engine/lsp/lsp/server.ts +2064 -2064
- package/src/engine/lsp/lsp.ts +507 -507
- package/src/engine/lsp/server.ts +2064 -2064
- package/src/engine/mcp/auth.ts +146 -146
- package/src/engine/mcp/index.ts +958 -958
- package/src/engine/mcp/mcp/auth.ts +146 -146
- package/src/engine/mcp/mcp/index.ts +958 -958
- package/src/engine/mcp/mcp/oauth-callback.ts +232 -232
- package/src/engine/mcp/mcp/oauth-provider.ts +214 -214
- package/src/engine/mcp/oauth-callback.ts +232 -232
- package/src/engine/mcp/oauth-provider.ts +214 -214
- package/src/engine/node.ts +6 -6
- package/src/engine/patch/index.ts +689 -689
- package/src/engine/patch/patch/index.ts +689 -689
- package/src/engine/permission/arity.ts +163 -163
- package/src/engine/permission/evaluate.ts +15 -15
- package/src/engine/permission/index.ts +306 -306
- package/src/engine/permission/permission/arity.ts +163 -163
- package/src/engine/permission/permission/evaluate.ts +15 -15
- package/src/engine/permission/permission/index.ts +306 -306
- package/src/engine/permission/permission/schema.ts +13 -13
- package/src/engine/permission/schema.ts +13 -13
- package/src/engine/plugin/azure.ts +26 -26
- package/src/engine/plugin/cloudflare.ts +76 -76
- package/src/engine/plugin/codex.ts +622 -622
- package/src/engine/plugin/digitalocean.ts +411 -411
- package/src/engine/plugin/github-copilot/copilot.ts +394 -394
- package/src/engine/plugin/github-copilot/models.ts +196 -196
- package/src/engine/plugin/index.ts +295 -295
- package/src/engine/plugin/install.ts +439 -439
- package/src/engine/plugin/loader.ts +216 -216
- package/src/engine/plugin/meta.ts +188 -188
- package/src/engine/plugin/shared.ts +323 -323
- package/src/engine/project/bootstrap-service.ts +9 -9
- package/src/engine/project/bootstrap.ts +75 -75
- package/src/engine/project/instance-context.ts +24 -24
- package/src/engine/project/instance-layer.ts +11 -11
- package/src/engine/project/instance-runtime.ts +16 -16
- package/src/engine/project/instance-store.ts +193 -193
- package/src/engine/project/project.sql.ts +17 -17
- package/src/engine/project/project.ts +537 -537
- package/src/engine/project/schema.ts +13 -13
- package/src/engine/project/vcs.ts +405 -405
- package/src/engine/provider/auth.ts +225 -225
- package/src/engine/provider/error.ts +204 -204
- package/src/engine/provider/model-status.ts +8 -8
- package/src/engine/provider/provider.ts +1843 -1843
- package/src/engine/provider/schema.ts +30 -30
- package/src/engine/provider/transform.ts +1376 -1376
- package/src/engine/pty/index.ts +365 -365
- package/src/engine/pty/input.ts +24 -24
- package/src/engine/pty/pty/index.ts +365 -365
- package/src/engine/pty/pty/input.ts +24 -24
- package/src/engine/pty/pty/pty.bun.ts +26 -26
- package/src/engine/pty/pty/pty.node.ts +27 -27
- package/src/engine/pty/pty/pty.ts +25 -25
- package/src/engine/pty/pty/schema.ts +14 -14
- package/src/engine/pty/pty/ticket.ts +68 -68
- package/src/engine/pty/pty.bun.ts +26 -26
- package/src/engine/pty/pty.node.ts +27 -27
- package/src/engine/pty/pty.ts +25 -25
- package/src/engine/pty/schema.ts +14 -14
- package/src/engine/pty/ticket.ts +68 -68
- package/src/engine/question/index.ts +213 -213
- package/src/engine/question/question/index.ts +213 -213
- package/src/engine/question/question/schema.ts +10 -10
- package/src/engine/question/schema.ts +10 -10
- package/src/engine/reference/reference/reference.ts +241 -241
- package/src/engine/reference/reference/repository-cache.ts +147 -147
- package/src/engine/reference/reference.ts +241 -241
- package/src/engine/reference/repository-cache.ts +147 -147
- package/src/engine/session/compaction.ts +651 -651
- package/src/engine/session/instruction.ts +238 -238
- package/src/engine/session/llm.ts +459 -459
- package/src/engine/session/message-error.ts +14 -14
- package/src/engine/session/message-v2.ts +1202 -1202
- package/src/engine/session/message.ts +146 -146
- package/src/engine/session/overflow.ts +32 -32
- package/src/engine/session/processor.ts +823 -823
- package/src/engine/session/prompt/anthropic.txt +105 -105
- package/src/engine/session/prompt/beast.txt +147 -147
- package/src/engine/session/prompt/build-switch.txt +5 -5
- package/src/engine/session/prompt/codex.txt +79 -79
- package/src/engine/session/prompt/copilot-gpt-5.txt +143 -143
- package/src/engine/session/prompt/default.txt +105 -105
- package/src/engine/session/prompt/gemini.txt +155 -155
- package/src/engine/session/prompt/gpt.txt +107 -107
- package/src/engine/session/prompt/kimi.txt +95 -95
- package/src/engine/session/prompt/max-steps.txt +15 -15
- package/src/engine/session/prompt/plan-reminder-anthropic.txt +67 -67
- package/src/engine/session/prompt/plan.txt +26 -26
- package/src/engine/session/prompt/trinity.txt +97 -97
- package/src/engine/session/prompt.ts +66 -9
- package/src/engine/session/retry.ts +200 -200
- package/src/engine/session/revert.ts +162 -162
- package/src/engine/session/run-state.ts +153 -153
- package/src/engine/session/schema.ts +26 -26
- package/src/engine/session/session.sql.ts +137 -137
- package/src/engine/session/session.ts +1011 -1011
- package/src/engine/session/status.ts +94 -94
- package/src/engine/session/summary.ts +164 -164
- package/src/engine/session/system.ts +84 -84
- package/src/engine/session/todo.ts +81 -81
- package/src/engine/share/session.ts +61 -61
- package/src/engine/share/share-next.ts +376 -376
- package/src/engine/share/share.sql.ts +13 -13
- package/src/engine/shell/shell/shell.ts +215 -215
- package/src/engine/shell/shell.ts +215 -215
- package/src/engine/skill/discovery.ts +116 -116
- package/src/engine/skill/index.ts +336 -336
- package/src/engine/skill/prompt/customize-opencode.md +377 -377
- package/src/engine/skill/skill/discovery.ts +116 -116
- package/src/engine/skill/skill/index.ts +336 -336
- package/src/engine/skill/skill/prompt/customize-opencode.md +377 -377
- package/src/engine/snapshot/index.ts +762 -762
- package/src/engine/snapshot/snapshot/index.ts +762 -762
- package/src/engine/sync/README.md +179 -179
- package/src/engine/sync/event.sql.ts +17 -17
- package/src/engine/sync/index.ts +410 -410
- package/src/engine/sync/schema.ts +11 -11
- package/src/engine/temporary.ts +33 -33
- package/src/engine/tool/apply_patch.ts +313 -313
- package/src/engine/tool/apply_patch.txt +33 -33
- package/src/engine/tool/edit.ts +711 -711
- package/src/engine/tool/edit.txt +10 -10
- package/src/engine/tool/external-directory.ts +49 -49
- package/src/engine/tool/glob.ts +103 -103
- package/src/engine/tool/glob.txt +6 -6
- package/src/engine/tool/grep.ts +156 -156
- package/src/engine/tool/grep.txt +8 -8
- package/src/engine/tool/invalid.ts +21 -21
- package/src/engine/tool/json-schema.ts +164 -164
- package/src/engine/tool/lsp.ts +113 -113
- package/src/engine/tool/lsp.txt +24 -24
- package/src/engine/tool/mcp-websearch.ts +96 -96
- package/src/engine/tool/plan-enter.txt +14 -14
- package/src/engine/tool/plan-exit.txt +13 -13
- package/src/engine/tool/plan.ts +78 -78
- package/src/engine/tool/question.ts +44 -44
- package/src/engine/tool/question.txt +10 -10
- package/src/engine/tool/read.ts +337 -337
- package/src/engine/tool/read.txt +14 -14
- package/src/engine/tool/registry.ts +472 -472
- package/src/engine/tool/repo_clone.ts +80 -80
- package/src/engine/tool/repo_clone.txt +5 -5
- package/src/engine/tool/repo_overview.ts +279 -279
- package/src/engine/tool/repo_overview.txt +4 -4
- package/src/engine/tool/schema.ts +14 -14
- package/src/engine/tool/shell/id.ts +19 -19
- package/src/engine/tool/shell/prompt.ts +295 -295
- package/src/engine/tool/shell/shell.txt +77 -77
- package/src/engine/tool/shell.ts +647 -647
- package/src/engine/tool/skill.ts +75 -75
- package/src/engine/tool/skill.txt +5 -5
- package/src/engine/tool/task.ts +337 -337
- package/src/engine/tool/task.txt +58 -58
- package/src/engine/tool/task_status.ts +179 -179
- package/src/engine/tool/task_status.txt +13 -13
- package/src/engine/tool/todo.ts +57 -57
- package/src/engine/tool/todowrite.txt +167 -167
- package/src/engine/tool/tool/apply_patch.ts +313 -313
- package/src/engine/tool/tool/apply_patch.txt +33 -33
- package/src/engine/tool/tool/edit.ts +711 -711
- package/src/engine/tool/tool/edit.txt +10 -10
- package/src/engine/tool/tool/external-directory.ts +49 -49
- package/src/engine/tool/tool/glob.ts +103 -103
- package/src/engine/tool/tool/glob.txt +6 -6
- package/src/engine/tool/tool/grep.ts +156 -156
- package/src/engine/tool/tool/grep.txt +8 -8
- package/src/engine/tool/tool/invalid.ts +21 -21
- package/src/engine/tool/tool/json-schema.ts +164 -164
- package/src/engine/tool/tool/lsp.ts +113 -113
- package/src/engine/tool/tool/lsp.txt +24 -24
- package/src/engine/tool/tool/mcp-websearch.ts +96 -96
- package/src/engine/tool/tool/plan-enter.txt +14 -14
- package/src/engine/tool/tool/plan-exit.txt +13 -13
- package/src/engine/tool/tool/plan.ts +78 -78
- package/src/engine/tool/tool/question.ts +44 -44
- package/src/engine/tool/tool/question.txt +10 -10
- package/src/engine/tool/tool/read.ts +337 -337
- package/src/engine/tool/tool/read.txt +14 -14
- package/src/engine/tool/tool/registry.ts +472 -472
- package/src/engine/tool/tool/repo_clone.ts +80 -80
- package/src/engine/tool/tool/repo_clone.txt +5 -5
- package/src/engine/tool/tool/repo_overview.ts +279 -279
- package/src/engine/tool/tool/repo_overview.txt +4 -4
- package/src/engine/tool/tool/schema.ts +14 -14
- package/src/engine/tool/tool/shell/id.ts +19 -19
- package/src/engine/tool/tool/shell/prompt.ts +295 -295
- package/src/engine/tool/tool/shell/shell.txt +77 -77
- package/src/engine/tool/tool/shell.ts +647 -647
- package/src/engine/tool/tool/skill.ts +75 -75
- package/src/engine/tool/tool/skill.txt +5 -5
- package/src/engine/tool/tool/task.ts +337 -337
- package/src/engine/tool/tool/task.txt +58 -58
- package/src/engine/tool/tool/task_status.ts +179 -179
- package/src/engine/tool/tool/task_status.txt +13 -13
- package/src/engine/tool/tool/todo.ts +57 -57
- package/src/engine/tool/tool/todowrite.txt +167 -167
- package/src/engine/tool/tool/tool.ts +164 -164
- package/src/engine/tool/tool/truncate.ts +160 -160
- package/src/engine/tool/tool/truncation-dir.ts +4 -4
- package/src/engine/tool/tool/webfetch.ts +192 -192
- package/src/engine/tool/tool/webfetch.txt +13 -13
- package/src/engine/tool/tool/websearch.ts +143 -143
- package/src/engine/tool/tool/websearch.txt +14 -14
- package/src/engine/tool/tool/write.ts +104 -104
- package/src/engine/tool/tool/write.txt +8 -8
- package/src/engine/tool/tool.ts +164 -164
- package/src/engine/tool/truncate.ts +160 -160
- package/src/engine/tool/truncation-dir.ts +4 -4
- package/src/engine/tool/webfetch.ts +192 -192
- package/src/engine/tool/webfetch.txt +13 -13
- package/src/engine/tool/websearch.ts +143 -143
- package/src/engine/tool/websearch.txt +14 -14
- package/src/engine/tool/write.ts +104 -104
- package/src/engine/tool/write.txt +8 -8
- package/src/engine/util/archive.ts +17 -17
- package/src/engine/util/bom.ts +31 -31
- package/src/engine/util/data-url.ts +9 -9
- package/src/engine/util/defer.ts +10 -10
- package/src/engine/util/effect-http-client.ts +11 -11
- package/src/engine/util/error.ts +88 -88
- package/src/engine/util/filesystem.ts +252 -252
- package/src/engine/util/format.ts +20 -20
- package/src/engine/util/iife.ts +3 -3
- package/src/engine/util/lazy.ts +20 -20
- package/src/engine/util/local-context.ts +25 -25
- package/src/engine/util/locale.ts +86 -86
- package/src/engine/util/media.ts +26 -26
- package/src/engine/util/process.ts +176 -176
- package/src/engine/util/queue.ts +32 -32
- package/src/engine/util/record.ts +3 -3
- package/src/engine/util/repository.ts +158 -158
- package/src/engine/util/rpc.ts +66 -66
- package/src/engine/util/signal.ts +12 -12
- package/src/engine/util/timeout.ts +13 -13
- package/src/engine/util/token.ts +7 -7
- package/src/engine/util/util/archive.ts +17 -17
- package/src/engine/util/util/bom.ts +31 -31
- package/src/engine/util/util/data-url.ts +9 -9
- package/src/engine/util/util/defer.ts +10 -10
- package/src/engine/util/util/effect-http-client.ts +11 -11
- package/src/engine/util/util/error.ts +88 -88
- package/src/engine/util/util/filesystem.ts +252 -252
- package/src/engine/util/util/format.ts +20 -20
- package/src/engine/util/util/iife.ts +3 -3
- package/src/engine/util/util/lazy.ts +20 -20
- package/src/engine/util/util/local-context.ts +25 -25
- package/src/engine/util/util/locale.ts +86 -86
- package/src/engine/util/util/media.ts +26 -26
- package/src/engine/util/util/process.ts +176 -176
- package/src/engine/util/util/queue.ts +32 -32
- package/src/engine/util/util/record.ts +3 -3
- package/src/engine/util/util/repository.ts +158 -158
- package/src/engine/util/util/rpc.ts +66 -66
- package/src/engine/util/util/signal.ts +12 -12
- package/src/engine/util/util/timeout.ts +13 -13
- package/src/engine/util/util/token.ts +7 -7
- package/src/engine/util/util/which.ts +14 -14
- package/src/engine/util/util/wildcard.ts +59 -59
- package/src/engine/util/which.ts +14 -14
- package/src/engine/util/wildcard.ts +59 -59
- package/src/engine/worktree/index.ts +621 -621
- package/src/server.ts +121 -158
- package/src/tui.ts +485 -502
|
@@ -1,651 +1,651 @@
|
|
|
1
|
-
import { BusEvent } from "@/bus/bus-event"
|
|
2
|
-
import { Bus } from "@/bus"
|
|
3
|
-
import * as Session from "./session"
|
|
4
|
-
import { SessionID, MessageID, PartID } from "./schema"
|
|
5
|
-
import { Provider } from "@/provider/provider"
|
|
6
|
-
import { MessageV2 } from "./message-v2"
|
|
7
|
-
import { Token } from "@/util/token"
|
|
8
|
-
import * as Log from "@opencode-ai/core/util/log"
|
|
9
|
-
import { SessionProcessor } from "./processor"
|
|
10
|
-
import { Agent } from "@/agent/agent"
|
|
11
|
-
import { Plugin } from "@/plugin"
|
|
12
|
-
import { Config } from "@/config/config"
|
|
13
|
-
import { NotFoundError } from "@/storage/storage"
|
|
14
|
-
import { ModelID, ProviderID } from "@/provider/schema"
|
|
15
|
-
import { Effect, Layer, Context, Schema } from "effect"
|
|
16
|
-
import * as DateTime from "effect/DateTime"
|
|
17
|
-
import { InstanceState } from "@/effect/instance-state"
|
|
18
|
-
import { isOverflow as overflow, usable } from "./overflow"
|
|
19
|
-
import { makeRuntime } from "@/effect/run-service"
|
|
20
|
-
import { serviceUse } from "@/effect/service-use"
|
|
21
|
-
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
22
|
-
import { EventV2 } from "@opencode-ai/core/event"
|
|
23
|
-
import { EventV2Bridge } from "@/event-v2-bridge"
|
|
24
|
-
import { SessionEvent } from "@opencode-ai/core/session-event"
|
|
25
|
-
|
|
26
|
-
const log = Log.create({ service: "session.compaction" })
|
|
27
|
-
|
|
28
|
-
export const Event = {
|
|
29
|
-
Compacted: BusEvent.define(
|
|
30
|
-
"session.compacted",
|
|
31
|
-
Schema.Struct({
|
|
32
|
-
sessionID: SessionID,
|
|
33
|
-
}),
|
|
34
|
-
),
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const PRUNE_MINIMUM = 20_000
|
|
38
|
-
export const PRUNE_PROTECT = 40_000
|
|
39
|
-
const TOOL_OUTPUT_MAX_CHARS = 2_000
|
|
40
|
-
const PRUNE_PROTECTED_TOOLS = ["skill"]
|
|
41
|
-
const DEFAULT_TAIL_TURNS = 2
|
|
42
|
-
const MIN_PRESERVE_RECENT_TOKENS = 2_000
|
|
43
|
-
const MAX_PRESERVE_RECENT_TOKENS = 8_000
|
|
44
|
-
const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
|
|
45
|
-
<template>
|
|
46
|
-
## Goal
|
|
47
|
-
- [single-sentence task summary]
|
|
48
|
-
|
|
49
|
-
## Constraints & Preferences
|
|
50
|
-
- [user constraints, preferences, specs, or "(none)"]
|
|
51
|
-
|
|
52
|
-
## Progress
|
|
53
|
-
### Done
|
|
54
|
-
- [completed work or "(none)"]
|
|
55
|
-
|
|
56
|
-
### In Progress
|
|
57
|
-
- [current work or "(none)"]
|
|
58
|
-
|
|
59
|
-
### Blocked
|
|
60
|
-
- [blockers or "(none)"]
|
|
61
|
-
|
|
62
|
-
## Key Decisions
|
|
63
|
-
- [decision and why, or "(none)"]
|
|
64
|
-
|
|
65
|
-
## Next Steps
|
|
66
|
-
- [ordered next actions or "(none)"]
|
|
67
|
-
|
|
68
|
-
## Critical Context
|
|
69
|
-
- [important technical facts, errors, open questions, or "(none)"]
|
|
70
|
-
|
|
71
|
-
## Relevant Files
|
|
72
|
-
- [file or directory path: why it matters, or "(none)"]
|
|
73
|
-
</template>
|
|
74
|
-
|
|
75
|
-
Rules:
|
|
76
|
-
- Keep every section, even when empty.
|
|
77
|
-
- Use terse bullets, not prose paragraphs.
|
|
78
|
-
- Preserve exact file paths, commands, error strings, and identifiers when known.
|
|
79
|
-
- Do not mention the summary process or that context was compacted.`
|
|
80
|
-
type Turn = {
|
|
81
|
-
start: number
|
|
82
|
-
end: number
|
|
83
|
-
id: MessageID
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
type Tail = {
|
|
87
|
-
start: number
|
|
88
|
-
id: MessageID
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
type CompletedCompaction = {
|
|
92
|
-
userIndex: number
|
|
93
|
-
assistantIndex: number
|
|
94
|
-
summary: string | undefined
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function summaryText(message: MessageV2.WithParts) {
|
|
98
|
-
const text = message.parts
|
|
99
|
-
.filter((part): part is MessageV2.TextPart => part.type === "text")
|
|
100
|
-
.map((part) => part.text.trim())
|
|
101
|
-
.filter(Boolean)
|
|
102
|
-
.join("\n\n")
|
|
103
|
-
.trim()
|
|
104
|
-
return text || undefined
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function completedCompactions(messages: MessageV2.WithParts[]) {
|
|
108
|
-
const users = new Map<MessageID, number>()
|
|
109
|
-
for (let i = 0; i < messages.length; i++) {
|
|
110
|
-
const msg = messages[i]
|
|
111
|
-
if (msg.info.role !== "user") continue
|
|
112
|
-
if (!msg.parts.some((part) => part.type === "compaction")) continue
|
|
113
|
-
users.set(msg.info.id, i)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return messages.flatMap((msg, assistantIndex): CompletedCompaction[] => {
|
|
117
|
-
if (msg.info.role !== "assistant") return []
|
|
118
|
-
if (!msg.info.summary || !msg.info.finish || msg.info.error) return []
|
|
119
|
-
const userIndex = users.get(msg.info.parentID)
|
|
120
|
-
if (userIndex === undefined) return []
|
|
121
|
-
return [{ userIndex, assistantIndex, summary: summaryText(msg) }]
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function buildPrompt(input: { previousSummary?: string; context: string[] }) {
|
|
126
|
-
const anchor = input.previousSummary
|
|
127
|
-
? [
|
|
128
|
-
"Update the anchored summary below using the conversation history above.",
|
|
129
|
-
"Preserve still-true details, remove stale details, and merge in the new facts.",
|
|
130
|
-
"<previous-summary>",
|
|
131
|
-
input.previousSummary,
|
|
132
|
-
"</previous-summary>",
|
|
133
|
-
].join("\n")
|
|
134
|
-
: "Create a new anchored summary from the conversation history above."
|
|
135
|
-
return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n")
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) {
|
|
139
|
-
return (
|
|
140
|
-
input.cfg.compaction?.preserve_recent_tokens ??
|
|
141
|
-
Math.min(MAX_PRESERVE_RECENT_TOKENS, Math.max(MIN_PRESERVE_RECENT_TOKENS, Math.floor(usable(input) * 0.25)))
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function turns(messages: MessageV2.WithParts[]) {
|
|
146
|
-
const result: Turn[] = []
|
|
147
|
-
for (let i = 0; i < messages.length; i++) {
|
|
148
|
-
const msg = messages[i]
|
|
149
|
-
if (msg.info.role !== "user") continue
|
|
150
|
-
if (msg.parts.some((part) => part.type === "compaction")) continue
|
|
151
|
-
result.push({
|
|
152
|
-
start: i,
|
|
153
|
-
end: messages.length,
|
|
154
|
-
id: msg.info.id,
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
for (let i = 0; i < result.length - 1; i++) {
|
|
158
|
-
result[i].end = result[i + 1].start
|
|
159
|
-
}
|
|
160
|
-
return result
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function splitTurn(input: {
|
|
164
|
-
messages: MessageV2.WithParts[]
|
|
165
|
-
turn: Turn
|
|
166
|
-
model: Provider.Model
|
|
167
|
-
budget: number
|
|
168
|
-
estimate: (input: { messages: MessageV2.WithParts[]; model: Provider.Model }) => Effect.Effect<number>
|
|
169
|
-
}) {
|
|
170
|
-
return Effect.gen(function* () {
|
|
171
|
-
if (input.budget <= 0) return undefined
|
|
172
|
-
if (input.turn.end - input.turn.start <= 1) return undefined
|
|
173
|
-
for (let start = input.turn.start + 1; start < input.turn.end; start++) {
|
|
174
|
-
const size = yield* input.estimate({
|
|
175
|
-
messages: input.messages.slice(start, input.turn.end),
|
|
176
|
-
model: input.model,
|
|
177
|
-
})
|
|
178
|
-
if (size > input.budget) continue
|
|
179
|
-
return {
|
|
180
|
-
start,
|
|
181
|
-
id: input.messages[start]!.info.id,
|
|
182
|
-
} satisfies Tail
|
|
183
|
-
}
|
|
184
|
-
return undefined
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export interface Interface {
|
|
189
|
-
readonly isOverflow: (input: {
|
|
190
|
-
tokens: MessageV2.Assistant["tokens"]
|
|
191
|
-
model: Provider.Model
|
|
192
|
-
}) => Effect.Effect<boolean>
|
|
193
|
-
readonly prune: (input: { sessionID: SessionID }) => Effect.Effect<void>
|
|
194
|
-
readonly process: (input: {
|
|
195
|
-
parentID: MessageID
|
|
196
|
-
messages: MessageV2.WithParts[]
|
|
197
|
-
sessionID: SessionID
|
|
198
|
-
auto: boolean
|
|
199
|
-
overflow?: boolean
|
|
200
|
-
}) => Effect.Effect<"continue" | "stop">
|
|
201
|
-
readonly create: (input: {
|
|
202
|
-
sessionID: SessionID
|
|
203
|
-
agent: string
|
|
204
|
-
model: { providerID: ProviderID; modelID: ModelID }
|
|
205
|
-
auto: boolean
|
|
206
|
-
overflow?: boolean
|
|
207
|
-
}) => Effect.Effect<void>
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
|
|
211
|
-
|
|
212
|
-
export const use = serviceUse(Service)
|
|
213
|
-
|
|
214
|
-
export const layer = Layer.effect(
|
|
215
|
-
Service,
|
|
216
|
-
Effect.gen(function* () {
|
|
217
|
-
const bus = yield* Bus.Service
|
|
218
|
-
const config = yield* Config.Service
|
|
219
|
-
const session = yield* Session.Service
|
|
220
|
-
const agents = yield* Agent.Service
|
|
221
|
-
const plugin = yield* Plugin.Service
|
|
222
|
-
const processors = yield* SessionProcessor.Service
|
|
223
|
-
const provider = yield* Provider.Service
|
|
224
|
-
const events = yield* EventV2Bridge.Service
|
|
225
|
-
const flags = yield* RuntimeFlags.Service
|
|
226
|
-
|
|
227
|
-
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
|
|
228
|
-
tokens: MessageV2.Assistant["tokens"]
|
|
229
|
-
model: Provider.Model
|
|
230
|
-
}) {
|
|
231
|
-
return overflow({
|
|
232
|
-
cfg: yield* config.get(),
|
|
233
|
-
tokens: input.tokens,
|
|
234
|
-
model: input.model,
|
|
235
|
-
outputTokenMax: flags.outputTokenMax,
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
const estimate = Effect.fn("SessionCompaction.estimate")(function* (input: {
|
|
240
|
-
messages: MessageV2.WithParts[]
|
|
241
|
-
model: Provider.Model
|
|
242
|
-
}) {
|
|
243
|
-
const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model)
|
|
244
|
-
return Token.estimate(JSON.stringify(msgs))
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
const select = Effect.fn("SessionCompaction.select")(function* (input: {
|
|
248
|
-
messages: MessageV2.WithParts[]
|
|
249
|
-
cfg: Config.Info
|
|
250
|
-
model: Provider.Model
|
|
251
|
-
}) {
|
|
252
|
-
const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS
|
|
253
|
-
if (limit <= 0) return { head: input.messages, tail_start_id: undefined }
|
|
254
|
-
const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model })
|
|
255
|
-
const all = turns(input.messages)
|
|
256
|
-
if (!all.length) return { head: input.messages, tail_start_id: undefined }
|
|
257
|
-
const recent = all.slice(-limit)
|
|
258
|
-
const sizes = yield* Effect.forEach(
|
|
259
|
-
recent,
|
|
260
|
-
(turn) =>
|
|
261
|
-
estimate({
|
|
262
|
-
messages: input.messages.slice(turn.start, turn.end),
|
|
263
|
-
model: input.model,
|
|
264
|
-
}),
|
|
265
|
-
{ concurrency: 1 },
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
let total = 0
|
|
269
|
-
let keep: Tail | undefined
|
|
270
|
-
for (let i = recent.length - 1; i >= 0; i--) {
|
|
271
|
-
const turn = recent[i]!
|
|
272
|
-
const size = sizes[i]
|
|
273
|
-
if (total + size <= budget) {
|
|
274
|
-
total += size
|
|
275
|
-
keep = { start: turn.start, id: turn.id }
|
|
276
|
-
continue
|
|
277
|
-
}
|
|
278
|
-
const remaining = budget - total
|
|
279
|
-
const split = yield* splitTurn({
|
|
280
|
-
messages: input.messages,
|
|
281
|
-
turn,
|
|
282
|
-
model: input.model,
|
|
283
|
-
budget: remaining,
|
|
284
|
-
estimate,
|
|
285
|
-
})
|
|
286
|
-
if (split) keep = split
|
|
287
|
-
else if (!keep) log.info("tail fallback", { budget, size, total })
|
|
288
|
-
break
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!keep || keep.start === 0) return { head: input.messages, tail_start_id: undefined }
|
|
292
|
-
return {
|
|
293
|
-
head: input.messages.slice(0, keep.start),
|
|
294
|
-
tail_start_id: keep.id,
|
|
295
|
-
}
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
// goes backwards through parts until there are PRUNE_PROTECT tokens worth of tool
|
|
299
|
-
// calls, then erases output of older tool calls to free context space
|
|
300
|
-
const prune = Effect.fn("SessionCompaction.prune")(function* (input: { sessionID: SessionID }) {
|
|
301
|
-
const cfg = yield* config.get()
|
|
302
|
-
if (!cfg.compaction?.prune) return
|
|
303
|
-
log.info("pruning")
|
|
304
|
-
|
|
305
|
-
const msgs = yield* session
|
|
306
|
-
.messages({ sessionID: input.sessionID })
|
|
307
|
-
.pipe(Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed(undefined)))
|
|
308
|
-
if (!msgs) return
|
|
309
|
-
|
|
310
|
-
let total = 0
|
|
311
|
-
let pruned = 0
|
|
312
|
-
const toPrune: MessageV2.ToolPart[] = []
|
|
313
|
-
let turns = 0
|
|
314
|
-
|
|
315
|
-
loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
|
|
316
|
-
const msg = msgs[msgIndex]
|
|
317
|
-
if (msg.info.role === "user") turns++
|
|
318
|
-
if (turns < 2) continue
|
|
319
|
-
if (msg.info.role === "assistant" && msg.info.summary) break loop
|
|
320
|
-
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
|
|
321
|
-
const part = msg.parts[partIndex]
|
|
322
|
-
if (part.type !== "tool") continue
|
|
323
|
-
if (part.state.status !== "completed") continue
|
|
324
|
-
if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue
|
|
325
|
-
if (part.state.time.compacted) break loop
|
|
326
|
-
const estimate = Token.estimate(part.state.output)
|
|
327
|
-
total += estimate
|
|
328
|
-
if (total <= PRUNE_PROTECT) continue
|
|
329
|
-
pruned += estimate
|
|
330
|
-
toPrune.push(part)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
log.info("found", { pruned, total })
|
|
335
|
-
if (pruned > PRUNE_MINIMUM) {
|
|
336
|
-
for (const part of toPrune) {
|
|
337
|
-
if (part.state.status === "completed") {
|
|
338
|
-
part.state.time.compacted = Date.now()
|
|
339
|
-
yield* session.updatePart(part)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
log.info("pruned", { count: toPrune.length })
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
const processCompaction = Effect.fn("SessionCompaction.process")(function* (input: {
|
|
347
|
-
parentID: MessageID
|
|
348
|
-
messages: MessageV2.WithParts[]
|
|
349
|
-
sessionID: SessionID
|
|
350
|
-
auto: boolean
|
|
351
|
-
overflow?: boolean
|
|
352
|
-
}) {
|
|
353
|
-
const parent = input.messages.findLast((m) => m.info.id === input.parentID)
|
|
354
|
-
if (!parent || parent.info.role !== "user") {
|
|
355
|
-
throw new Error(`Compaction parent must be a user message: ${input.parentID}`)
|
|
356
|
-
}
|
|
357
|
-
const userMessage = parent.info
|
|
358
|
-
const compactionPart = parent.parts.find((part): part is MessageV2.CompactionPart => part.type === "compaction")
|
|
359
|
-
|
|
360
|
-
let messages = input.messages
|
|
361
|
-
let replay:
|
|
362
|
-
| {
|
|
363
|
-
info: MessageV2.User
|
|
364
|
-
parts: MessageV2.Part[]
|
|
365
|
-
}
|
|
366
|
-
| undefined
|
|
367
|
-
if (input.overflow) {
|
|
368
|
-
const idx = input.messages.findIndex((m) => m.info.id === input.parentID)
|
|
369
|
-
for (let i = idx - 1; i >= 0; i--) {
|
|
370
|
-
const msg = input.messages[i]
|
|
371
|
-
if (msg.info.role === "user" && !msg.parts.some((p) => p.type === "compaction")) {
|
|
372
|
-
replay = { info: msg.info, parts: msg.parts }
|
|
373
|
-
messages = input.messages.slice(0, i)
|
|
374
|
-
break
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const hasContent =
|
|
378
|
-
replay && messages.some((m) => m.info.role === "user" && !m.parts.some((p) => p.type === "compaction"))
|
|
379
|
-
if (!hasContent) {
|
|
380
|
-
replay = undefined
|
|
381
|
-
messages = input.messages
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const agent = yield* agents.get("compaction")
|
|
386
|
-
const model = agent.model
|
|
387
|
-
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
|
|
388
|
-
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
|
|
389
|
-
const cfg = yield* config.get()
|
|
390
|
-
const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages
|
|
391
|
-
const prior = completedCompactions(history)
|
|
392
|
-
const hidden = new Set(prior.flatMap((item) => [item.userIndex, item.assistantIndex]))
|
|
393
|
-
const previousSummary = prior.at(-1)?.summary
|
|
394
|
-
const selected = yield* select({
|
|
395
|
-
messages: history.filter((_, index) => !hidden.has(index)),
|
|
396
|
-
cfg,
|
|
397
|
-
model,
|
|
398
|
-
})
|
|
399
|
-
// Allow plugins to inject context or replace compaction prompt.
|
|
400
|
-
const compacting = yield* plugin.trigger(
|
|
401
|
-
"experimental.session.compacting",
|
|
402
|
-
{ sessionID: input.sessionID },
|
|
403
|
-
{ context: [], prompt: undefined },
|
|
404
|
-
)
|
|
405
|
-
const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context })
|
|
406
|
-
const msgs = structuredClone(selected.head)
|
|
407
|
-
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
|
408
|
-
const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, {
|
|
409
|
-
stripMedia: true,
|
|
410
|
-
toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS,
|
|
411
|
-
})
|
|
412
|
-
const ctx = yield* InstanceState.context
|
|
413
|
-
const msg: MessageV2.Assistant = {
|
|
414
|
-
id: MessageID.ascending(),
|
|
415
|
-
role: "assistant",
|
|
416
|
-
parentID: input.parentID,
|
|
417
|
-
sessionID: input.sessionID,
|
|
418
|
-
mode: "compaction",
|
|
419
|
-
agent: "compaction",
|
|
420
|
-
variant: userMessage.model.variant,
|
|
421
|
-
summary: true,
|
|
422
|
-
path: {
|
|
423
|
-
cwd: ctx.directory,
|
|
424
|
-
root: ctx.worktree,
|
|
425
|
-
},
|
|
426
|
-
cost: 0,
|
|
427
|
-
tokens: {
|
|
428
|
-
output: 0,
|
|
429
|
-
input: 0,
|
|
430
|
-
reasoning: 0,
|
|
431
|
-
cache: { read: 0, write: 0 },
|
|
432
|
-
},
|
|
433
|
-
modelID: model.id,
|
|
434
|
-
providerID: model.providerID,
|
|
435
|
-
time: {
|
|
436
|
-
created: Date.now(),
|
|
437
|
-
},
|
|
438
|
-
}
|
|
439
|
-
yield* session.updateMessage(msg)
|
|
440
|
-
const processor = yield* processors.create({
|
|
441
|
-
assistantMessage: msg,
|
|
442
|
-
sessionID: input.sessionID,
|
|
443
|
-
model,
|
|
444
|
-
})
|
|
445
|
-
const result = yield* processor.process({
|
|
446
|
-
user: userMessage,
|
|
447
|
-
agent,
|
|
448
|
-
sessionID: input.sessionID,
|
|
449
|
-
tools: {},
|
|
450
|
-
system: [],
|
|
451
|
-
messages: [
|
|
452
|
-
...modelMessages,
|
|
453
|
-
{
|
|
454
|
-
role: "user",
|
|
455
|
-
content: [{ type: "text", text: nextPrompt }],
|
|
456
|
-
},
|
|
457
|
-
],
|
|
458
|
-
model,
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
if (result === "compact") {
|
|
462
|
-
processor.message.error = new MessageV2.ContextOverflowError({
|
|
463
|
-
message: replay
|
|
464
|
-
? "Conversation history too large to compact - exceeds model context limit"
|
|
465
|
-
: "Session too large to compact - context exceeds model limit even after stripping media",
|
|
466
|
-
}).toObject()
|
|
467
|
-
processor.message.finish = "error"
|
|
468
|
-
yield* session.updateMessage(processor.message)
|
|
469
|
-
return "stop"
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (compactionPart && selected.tail_start_id && compactionPart.tail_start_id !== selected.tail_start_id) {
|
|
473
|
-
yield* session.updatePart({
|
|
474
|
-
...compactionPart,
|
|
475
|
-
tail_start_id: selected.tail_start_id,
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (result === "continue" && input.auto) {
|
|
480
|
-
if (replay) {
|
|
481
|
-
const original = replay.info
|
|
482
|
-
const replayMsg = yield* session.updateMessage({
|
|
483
|
-
id: MessageID.ascending(),
|
|
484
|
-
role: "user",
|
|
485
|
-
sessionID: input.sessionID,
|
|
486
|
-
time: { created: Date.now() },
|
|
487
|
-
agent: original.agent,
|
|
488
|
-
model: original.model,
|
|
489
|
-
format: original.format,
|
|
490
|
-
tools: original.tools,
|
|
491
|
-
system: original.system,
|
|
492
|
-
})
|
|
493
|
-
for (const part of replay.parts) {
|
|
494
|
-
if (part.type === "compaction") continue
|
|
495
|
-
const replayPart =
|
|
496
|
-
part.type === "file" && MessageV2.isMedia(part.mime)
|
|
497
|
-
? { type: "text" as const, text: `[Attached ${part.mime}: ${part.filename ?? "file"}]` }
|
|
498
|
-
: part
|
|
499
|
-
yield* session.updatePart({
|
|
500
|
-
...replayPart,
|
|
501
|
-
id: PartID.ascending(),
|
|
502
|
-
messageID: replayMsg.id,
|
|
503
|
-
sessionID: input.sessionID,
|
|
504
|
-
})
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (!replay) {
|
|
509
|
-
const info = yield* provider.getProvider(userMessage.model.providerID)
|
|
510
|
-
if (
|
|
511
|
-
(yield* plugin.trigger(
|
|
512
|
-
"experimental.compaction.autocontinue",
|
|
513
|
-
{
|
|
514
|
-
sessionID: input.sessionID,
|
|
515
|
-
agent: userMessage.agent,
|
|
516
|
-
model: yield* provider
|
|
517
|
-
.getModel(userMessage.model.providerID, userMessage.model.modelID)
|
|
518
|
-
.pipe(Effect.orDie),
|
|
519
|
-
provider: {
|
|
520
|
-
source: info.source,
|
|
521
|
-
info,
|
|
522
|
-
options: info.options,
|
|
523
|
-
},
|
|
524
|
-
message: userMessage,
|
|
525
|
-
overflow: input.overflow === true,
|
|
526
|
-
},
|
|
527
|
-
{ enabled: true },
|
|
528
|
-
)).enabled
|
|
529
|
-
) {
|
|
530
|
-
const continueMsg = yield* session.updateMessage({
|
|
531
|
-
id: MessageID.ascending(),
|
|
532
|
-
role: "user",
|
|
533
|
-
sessionID: input.sessionID,
|
|
534
|
-
time: { created: Date.now() },
|
|
535
|
-
agent: userMessage.agent,
|
|
536
|
-
model: userMessage.model,
|
|
537
|
-
})
|
|
538
|
-
const text =
|
|
539
|
-
(input.overflow
|
|
540
|
-
? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n"
|
|
541
|
-
: "") +
|
|
542
|
-
"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
|
|
543
|
-
yield* session.updatePart({
|
|
544
|
-
id: PartID.ascending(),
|
|
545
|
-
messageID: continueMsg.id,
|
|
546
|
-
sessionID: input.sessionID,
|
|
547
|
-
type: "text",
|
|
548
|
-
// Internal marker for auto-compaction followups so provider plugins
|
|
549
|
-
// can distinguish them from manual post-compaction user prompts.
|
|
550
|
-
// This is not a stable plugin contract and may change or disappear.
|
|
551
|
-
metadata: { compaction_continue: true },
|
|
552
|
-
synthetic: true,
|
|
553
|
-
text,
|
|
554
|
-
time: {
|
|
555
|
-
start: Date.now(),
|
|
556
|
-
end: Date.now(),
|
|
557
|
-
},
|
|
558
|
-
})
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
if (processor.message.error) return "stop"
|
|
564
|
-
if (result === "continue") {
|
|
565
|
-
const summary = summaryText(
|
|
566
|
-
(yield* session.messages({ sessionID: input.sessionID }).pipe(Effect.orDie)).find(
|
|
567
|
-
(item) => item.info.id === msg.id,
|
|
568
|
-
) ?? {
|
|
569
|
-
info: msg,
|
|
570
|
-
parts: [],
|
|
571
|
-
},
|
|
572
|
-
)
|
|
573
|
-
if (flags.experimentalEventSystem) {
|
|
574
|
-
yield* events.publish(SessionEvent.Compaction.Ended, {
|
|
575
|
-
sessionID: input.sessionID,
|
|
576
|
-
timestamp: DateTime.makeUnsafe(Date.now()),
|
|
577
|
-
text: summary ?? "",
|
|
578
|
-
include: selected.tail_start_id,
|
|
579
|
-
})
|
|
580
|
-
}
|
|
581
|
-
yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
|
|
582
|
-
}
|
|
583
|
-
return result
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
const create = Effect.fn("SessionCompaction.create")(function* (input: {
|
|
587
|
-
sessionID: SessionID
|
|
588
|
-
agent: string
|
|
589
|
-
model: { providerID: ProviderID; modelID: ModelID }
|
|
590
|
-
auto: boolean
|
|
591
|
-
overflow?: boolean
|
|
592
|
-
}) {
|
|
593
|
-
const msg = yield* session.updateMessage({
|
|
594
|
-
id: MessageID.ascending(),
|
|
595
|
-
role: "user",
|
|
596
|
-
model: input.model,
|
|
597
|
-
sessionID: input.sessionID,
|
|
598
|
-
agent: input.agent,
|
|
599
|
-
time: { created: Date.now() },
|
|
600
|
-
})
|
|
601
|
-
yield* session.updatePart({
|
|
602
|
-
id: PartID.ascending(),
|
|
603
|
-
messageID: msg.id,
|
|
604
|
-
sessionID: msg.sessionID,
|
|
605
|
-
type: "compaction",
|
|
606
|
-
auto: input.auto,
|
|
607
|
-
overflow: input.overflow,
|
|
608
|
-
})
|
|
609
|
-
if (flags.experimentalEventSystem) {
|
|
610
|
-
yield* events.publish(SessionEvent.Compaction.Started, {
|
|
611
|
-
sessionID: input.sessionID,
|
|
612
|
-
timestamp: DateTime.makeUnsafe(Date.now()),
|
|
613
|
-
reason: input.auto ? "auto" : "manual",
|
|
614
|
-
})
|
|
615
|
-
}
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
return Service.of({
|
|
619
|
-
isOverflow,
|
|
620
|
-
prune,
|
|
621
|
-
process: processCompaction,
|
|
622
|
-
create,
|
|
623
|
-
})
|
|
624
|
-
}),
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
export const defaultLayer = Layer.suspend(() =>
|
|
628
|
-
layer.pipe(
|
|
629
|
-
Layer.provide(Provider.defaultLayer),
|
|
630
|
-
Layer.provide(Session.defaultLayer),
|
|
631
|
-
Layer.provide(SessionProcessor.defaultLayer),
|
|
632
|
-
Layer.provide(Agent.defaultLayer),
|
|
633
|
-
Layer.provide(Plugin.defaultLayer),
|
|
634
|
-
Layer.provide(Bus.layer),
|
|
635
|
-
Layer.provide(Config.defaultLayer),
|
|
636
|
-
Layer.provide(RuntimeFlags.defaultLayer),
|
|
637
|
-
Layer.provide(EventV2Bridge.defaultLayer),
|
|
638
|
-
),
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
const { runPromise } = makeRuntime(Service, defaultLayer)
|
|
642
|
-
|
|
643
|
-
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
|
|
644
|
-
return runPromise((svc) => svc.isOverflow(input))
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
export async function prune(input: { sessionID: SessionID }) {
|
|
648
|
-
return runPromise((svc) => svc.prune(input))
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
export * as SessionCompaction from "./compaction"
|
|
1
|
+
import { BusEvent } from "@/bus/bus-event"
|
|
2
|
+
import { Bus } from "@/bus"
|
|
3
|
+
import * as Session from "./session"
|
|
4
|
+
import { SessionID, MessageID, PartID } from "./schema"
|
|
5
|
+
import { Provider } from "@/provider/provider"
|
|
6
|
+
import { MessageV2 } from "./message-v2"
|
|
7
|
+
import { Token } from "@/util/token"
|
|
8
|
+
import * as Log from "@opencode-ai/core/util/log"
|
|
9
|
+
import { SessionProcessor } from "./processor"
|
|
10
|
+
import { Agent } from "@/agent/agent"
|
|
11
|
+
import { Plugin } from "@/plugin"
|
|
12
|
+
import { Config } from "@/config/config"
|
|
13
|
+
import { NotFoundError } from "@/storage/storage"
|
|
14
|
+
import { ModelID, ProviderID } from "@/provider/schema"
|
|
15
|
+
import { Effect, Layer, Context, Schema } from "effect"
|
|
16
|
+
import * as DateTime from "effect/DateTime"
|
|
17
|
+
import { InstanceState } from "@/effect/instance-state"
|
|
18
|
+
import { isOverflow as overflow, usable } from "./overflow"
|
|
19
|
+
import { makeRuntime } from "@/effect/run-service"
|
|
20
|
+
import { serviceUse } from "@/effect/service-use"
|
|
21
|
+
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
22
|
+
import { EventV2 } from "@opencode-ai/core/event"
|
|
23
|
+
import { EventV2Bridge } from "@/event-v2-bridge"
|
|
24
|
+
import { SessionEvent } from "@opencode-ai/core/session-event"
|
|
25
|
+
|
|
26
|
+
const log = Log.create({ service: "session.compaction" })
|
|
27
|
+
|
|
28
|
+
export const Event = {
|
|
29
|
+
Compacted: BusEvent.define(
|
|
30
|
+
"session.compacted",
|
|
31
|
+
Schema.Struct({
|
|
32
|
+
sessionID: SessionID,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const PRUNE_MINIMUM = 20_000
|
|
38
|
+
export const PRUNE_PROTECT = 40_000
|
|
39
|
+
const TOOL_OUTPUT_MAX_CHARS = 2_000
|
|
40
|
+
const PRUNE_PROTECTED_TOOLS = ["skill"]
|
|
41
|
+
const DEFAULT_TAIL_TURNS = 2
|
|
42
|
+
const MIN_PRESERVE_RECENT_TOKENS = 2_000
|
|
43
|
+
const MAX_PRESERVE_RECENT_TOKENS = 8_000
|
|
44
|
+
const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
|
|
45
|
+
<template>
|
|
46
|
+
## Goal
|
|
47
|
+
- [single-sentence task summary]
|
|
48
|
+
|
|
49
|
+
## Constraints & Preferences
|
|
50
|
+
- [user constraints, preferences, specs, or "(none)"]
|
|
51
|
+
|
|
52
|
+
## Progress
|
|
53
|
+
### Done
|
|
54
|
+
- [completed work or "(none)"]
|
|
55
|
+
|
|
56
|
+
### In Progress
|
|
57
|
+
- [current work or "(none)"]
|
|
58
|
+
|
|
59
|
+
### Blocked
|
|
60
|
+
- [blockers or "(none)"]
|
|
61
|
+
|
|
62
|
+
## Key Decisions
|
|
63
|
+
- [decision and why, or "(none)"]
|
|
64
|
+
|
|
65
|
+
## Next Steps
|
|
66
|
+
- [ordered next actions or "(none)"]
|
|
67
|
+
|
|
68
|
+
## Critical Context
|
|
69
|
+
- [important technical facts, errors, open questions, or "(none)"]
|
|
70
|
+
|
|
71
|
+
## Relevant Files
|
|
72
|
+
- [file or directory path: why it matters, or "(none)"]
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
Rules:
|
|
76
|
+
- Keep every section, even when empty.
|
|
77
|
+
- Use terse bullets, not prose paragraphs.
|
|
78
|
+
- Preserve exact file paths, commands, error strings, and identifiers when known.
|
|
79
|
+
- Do not mention the summary process or that context was compacted.`
|
|
80
|
+
type Turn = {
|
|
81
|
+
start: number
|
|
82
|
+
end: number
|
|
83
|
+
id: MessageID
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Tail = {
|
|
87
|
+
start: number
|
|
88
|
+
id: MessageID
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type CompletedCompaction = {
|
|
92
|
+
userIndex: number
|
|
93
|
+
assistantIndex: number
|
|
94
|
+
summary: string | undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function summaryText(message: MessageV2.WithParts) {
|
|
98
|
+
const text = message.parts
|
|
99
|
+
.filter((part): part is MessageV2.TextPart => part.type === "text")
|
|
100
|
+
.map((part) => part.text.trim())
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.join("\n\n")
|
|
103
|
+
.trim()
|
|
104
|
+
return text || undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function completedCompactions(messages: MessageV2.WithParts[]) {
|
|
108
|
+
const users = new Map<MessageID, number>()
|
|
109
|
+
for (let i = 0; i < messages.length; i++) {
|
|
110
|
+
const msg = messages[i]
|
|
111
|
+
if (msg.info.role !== "user") continue
|
|
112
|
+
if (!msg.parts.some((part) => part.type === "compaction")) continue
|
|
113
|
+
users.set(msg.info.id, i)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return messages.flatMap((msg, assistantIndex): CompletedCompaction[] => {
|
|
117
|
+
if (msg.info.role !== "assistant") return []
|
|
118
|
+
if (!msg.info.summary || !msg.info.finish || msg.info.error) return []
|
|
119
|
+
const userIndex = users.get(msg.info.parentID)
|
|
120
|
+
if (userIndex === undefined) return []
|
|
121
|
+
return [{ userIndex, assistantIndex, summary: summaryText(msg) }]
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildPrompt(input: { previousSummary?: string; context: string[] }) {
|
|
126
|
+
const anchor = input.previousSummary
|
|
127
|
+
? [
|
|
128
|
+
"Update the anchored summary below using the conversation history above.",
|
|
129
|
+
"Preserve still-true details, remove stale details, and merge in the new facts.",
|
|
130
|
+
"<previous-summary>",
|
|
131
|
+
input.previousSummary,
|
|
132
|
+
"</previous-summary>",
|
|
133
|
+
].join("\n")
|
|
134
|
+
: "Create a new anchored summary from the conversation history above."
|
|
135
|
+
return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) {
|
|
139
|
+
return (
|
|
140
|
+
input.cfg.compaction?.preserve_recent_tokens ??
|
|
141
|
+
Math.min(MAX_PRESERVE_RECENT_TOKENS, Math.max(MIN_PRESERVE_RECENT_TOKENS, Math.floor(usable(input) * 0.25)))
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function turns(messages: MessageV2.WithParts[]) {
|
|
146
|
+
const result: Turn[] = []
|
|
147
|
+
for (let i = 0; i < messages.length; i++) {
|
|
148
|
+
const msg = messages[i]
|
|
149
|
+
if (msg.info.role !== "user") continue
|
|
150
|
+
if (msg.parts.some((part) => part.type === "compaction")) continue
|
|
151
|
+
result.push({
|
|
152
|
+
start: i,
|
|
153
|
+
end: messages.length,
|
|
154
|
+
id: msg.info.id,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
for (let i = 0; i < result.length - 1; i++) {
|
|
158
|
+
result[i].end = result[i + 1].start
|
|
159
|
+
}
|
|
160
|
+
return result
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function splitTurn(input: {
|
|
164
|
+
messages: MessageV2.WithParts[]
|
|
165
|
+
turn: Turn
|
|
166
|
+
model: Provider.Model
|
|
167
|
+
budget: number
|
|
168
|
+
estimate: (input: { messages: MessageV2.WithParts[]; model: Provider.Model }) => Effect.Effect<number>
|
|
169
|
+
}) {
|
|
170
|
+
return Effect.gen(function* () {
|
|
171
|
+
if (input.budget <= 0) return undefined
|
|
172
|
+
if (input.turn.end - input.turn.start <= 1) return undefined
|
|
173
|
+
for (let start = input.turn.start + 1; start < input.turn.end; start++) {
|
|
174
|
+
const size = yield* input.estimate({
|
|
175
|
+
messages: input.messages.slice(start, input.turn.end),
|
|
176
|
+
model: input.model,
|
|
177
|
+
})
|
|
178
|
+
if (size > input.budget) continue
|
|
179
|
+
return {
|
|
180
|
+
start,
|
|
181
|
+
id: input.messages[start]!.info.id,
|
|
182
|
+
} satisfies Tail
|
|
183
|
+
}
|
|
184
|
+
return undefined
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface Interface {
|
|
189
|
+
readonly isOverflow: (input: {
|
|
190
|
+
tokens: MessageV2.Assistant["tokens"]
|
|
191
|
+
model: Provider.Model
|
|
192
|
+
}) => Effect.Effect<boolean>
|
|
193
|
+
readonly prune: (input: { sessionID: SessionID }) => Effect.Effect<void>
|
|
194
|
+
readonly process: (input: {
|
|
195
|
+
parentID: MessageID
|
|
196
|
+
messages: MessageV2.WithParts[]
|
|
197
|
+
sessionID: SessionID
|
|
198
|
+
auto: boolean
|
|
199
|
+
overflow?: boolean
|
|
200
|
+
}) => Effect.Effect<"continue" | "stop">
|
|
201
|
+
readonly create: (input: {
|
|
202
|
+
sessionID: SessionID
|
|
203
|
+
agent: string
|
|
204
|
+
model: { providerID: ProviderID; modelID: ModelID }
|
|
205
|
+
auto: boolean
|
|
206
|
+
overflow?: boolean
|
|
207
|
+
}) => Effect.Effect<void>
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
|
|
211
|
+
|
|
212
|
+
export const use = serviceUse(Service)
|
|
213
|
+
|
|
214
|
+
export const layer = Layer.effect(
|
|
215
|
+
Service,
|
|
216
|
+
Effect.gen(function* () {
|
|
217
|
+
const bus = yield* Bus.Service
|
|
218
|
+
const config = yield* Config.Service
|
|
219
|
+
const session = yield* Session.Service
|
|
220
|
+
const agents = yield* Agent.Service
|
|
221
|
+
const plugin = yield* Plugin.Service
|
|
222
|
+
const processors = yield* SessionProcessor.Service
|
|
223
|
+
const provider = yield* Provider.Service
|
|
224
|
+
const events = yield* EventV2Bridge.Service
|
|
225
|
+
const flags = yield* RuntimeFlags.Service
|
|
226
|
+
|
|
227
|
+
const isOverflow = Effect.fn("SessionCompaction.isOverflow")(function* (input: {
|
|
228
|
+
tokens: MessageV2.Assistant["tokens"]
|
|
229
|
+
model: Provider.Model
|
|
230
|
+
}) {
|
|
231
|
+
return overflow({
|
|
232
|
+
cfg: yield* config.get(),
|
|
233
|
+
tokens: input.tokens,
|
|
234
|
+
model: input.model,
|
|
235
|
+
outputTokenMax: flags.outputTokenMax,
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const estimate = Effect.fn("SessionCompaction.estimate")(function* (input: {
|
|
240
|
+
messages: MessageV2.WithParts[]
|
|
241
|
+
model: Provider.Model
|
|
242
|
+
}) {
|
|
243
|
+
const msgs = yield* MessageV2.toModelMessagesEffect(input.messages, input.model)
|
|
244
|
+
return Token.estimate(JSON.stringify(msgs))
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const select = Effect.fn("SessionCompaction.select")(function* (input: {
|
|
248
|
+
messages: MessageV2.WithParts[]
|
|
249
|
+
cfg: Config.Info
|
|
250
|
+
model: Provider.Model
|
|
251
|
+
}) {
|
|
252
|
+
const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS
|
|
253
|
+
if (limit <= 0) return { head: input.messages, tail_start_id: undefined }
|
|
254
|
+
const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model })
|
|
255
|
+
const all = turns(input.messages)
|
|
256
|
+
if (!all.length) return { head: input.messages, tail_start_id: undefined }
|
|
257
|
+
const recent = all.slice(-limit)
|
|
258
|
+
const sizes = yield* Effect.forEach(
|
|
259
|
+
recent,
|
|
260
|
+
(turn) =>
|
|
261
|
+
estimate({
|
|
262
|
+
messages: input.messages.slice(turn.start, turn.end),
|
|
263
|
+
model: input.model,
|
|
264
|
+
}),
|
|
265
|
+
{ concurrency: 1 },
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
let total = 0
|
|
269
|
+
let keep: Tail | undefined
|
|
270
|
+
for (let i = recent.length - 1; i >= 0; i--) {
|
|
271
|
+
const turn = recent[i]!
|
|
272
|
+
const size = sizes[i]
|
|
273
|
+
if (total + size <= budget) {
|
|
274
|
+
total += size
|
|
275
|
+
keep = { start: turn.start, id: turn.id }
|
|
276
|
+
continue
|
|
277
|
+
}
|
|
278
|
+
const remaining = budget - total
|
|
279
|
+
const split = yield* splitTurn({
|
|
280
|
+
messages: input.messages,
|
|
281
|
+
turn,
|
|
282
|
+
model: input.model,
|
|
283
|
+
budget: remaining,
|
|
284
|
+
estimate,
|
|
285
|
+
})
|
|
286
|
+
if (split) keep = split
|
|
287
|
+
else if (!keep) log.info("tail fallback", { budget, size, total })
|
|
288
|
+
break
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!keep || keep.start === 0) return { head: input.messages, tail_start_id: undefined }
|
|
292
|
+
return {
|
|
293
|
+
head: input.messages.slice(0, keep.start),
|
|
294
|
+
tail_start_id: keep.id,
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// goes backwards through parts until there are PRUNE_PROTECT tokens worth of tool
|
|
299
|
+
// calls, then erases output of older tool calls to free context space
|
|
300
|
+
const prune = Effect.fn("SessionCompaction.prune")(function* (input: { sessionID: SessionID }) {
|
|
301
|
+
const cfg = yield* config.get()
|
|
302
|
+
if (!cfg.compaction?.prune) return
|
|
303
|
+
log.info("pruning")
|
|
304
|
+
|
|
305
|
+
const msgs = yield* session
|
|
306
|
+
.messages({ sessionID: input.sessionID })
|
|
307
|
+
.pipe(Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed(undefined)))
|
|
308
|
+
if (!msgs) return
|
|
309
|
+
|
|
310
|
+
let total = 0
|
|
311
|
+
let pruned = 0
|
|
312
|
+
const toPrune: MessageV2.ToolPart[] = []
|
|
313
|
+
let turns = 0
|
|
314
|
+
|
|
315
|
+
loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
|
|
316
|
+
const msg = msgs[msgIndex]
|
|
317
|
+
if (msg.info.role === "user") turns++
|
|
318
|
+
if (turns < 2) continue
|
|
319
|
+
if (msg.info.role === "assistant" && msg.info.summary) break loop
|
|
320
|
+
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
|
|
321
|
+
const part = msg.parts[partIndex]
|
|
322
|
+
if (part.type !== "tool") continue
|
|
323
|
+
if (part.state.status !== "completed") continue
|
|
324
|
+
if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue
|
|
325
|
+
if (part.state.time.compacted) break loop
|
|
326
|
+
const estimate = Token.estimate(part.state.output)
|
|
327
|
+
total += estimate
|
|
328
|
+
if (total <= PRUNE_PROTECT) continue
|
|
329
|
+
pruned += estimate
|
|
330
|
+
toPrune.push(part)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
log.info("found", { pruned, total })
|
|
335
|
+
if (pruned > PRUNE_MINIMUM) {
|
|
336
|
+
for (const part of toPrune) {
|
|
337
|
+
if (part.state.status === "completed") {
|
|
338
|
+
part.state.time.compacted = Date.now()
|
|
339
|
+
yield* session.updatePart(part)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
log.info("pruned", { count: toPrune.length })
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
const processCompaction = Effect.fn("SessionCompaction.process")(function* (input: {
|
|
347
|
+
parentID: MessageID
|
|
348
|
+
messages: MessageV2.WithParts[]
|
|
349
|
+
sessionID: SessionID
|
|
350
|
+
auto: boolean
|
|
351
|
+
overflow?: boolean
|
|
352
|
+
}) {
|
|
353
|
+
const parent = input.messages.findLast((m) => m.info.id === input.parentID)
|
|
354
|
+
if (!parent || parent.info.role !== "user") {
|
|
355
|
+
throw new Error(`Compaction parent must be a user message: ${input.parentID}`)
|
|
356
|
+
}
|
|
357
|
+
const userMessage = parent.info
|
|
358
|
+
const compactionPart = parent.parts.find((part): part is MessageV2.CompactionPart => part.type === "compaction")
|
|
359
|
+
|
|
360
|
+
let messages = input.messages
|
|
361
|
+
let replay:
|
|
362
|
+
| {
|
|
363
|
+
info: MessageV2.User
|
|
364
|
+
parts: MessageV2.Part[]
|
|
365
|
+
}
|
|
366
|
+
| undefined
|
|
367
|
+
if (input.overflow) {
|
|
368
|
+
const idx = input.messages.findIndex((m) => m.info.id === input.parentID)
|
|
369
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
370
|
+
const msg = input.messages[i]
|
|
371
|
+
if (msg.info.role === "user" && !msg.parts.some((p) => p.type === "compaction")) {
|
|
372
|
+
replay = { info: msg.info, parts: msg.parts }
|
|
373
|
+
messages = input.messages.slice(0, i)
|
|
374
|
+
break
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const hasContent =
|
|
378
|
+
replay && messages.some((m) => m.info.role === "user" && !m.parts.some((p) => p.type === "compaction"))
|
|
379
|
+
if (!hasContent) {
|
|
380
|
+
replay = undefined
|
|
381
|
+
messages = input.messages
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const agent = yield* agents.get("compaction")
|
|
386
|
+
const model = agent.model
|
|
387
|
+
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
|
|
388
|
+
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
|
|
389
|
+
const cfg = yield* config.get()
|
|
390
|
+
const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages
|
|
391
|
+
const prior = completedCompactions(history)
|
|
392
|
+
const hidden = new Set(prior.flatMap((item) => [item.userIndex, item.assistantIndex]))
|
|
393
|
+
const previousSummary = prior.at(-1)?.summary
|
|
394
|
+
const selected = yield* select({
|
|
395
|
+
messages: history.filter((_, index) => !hidden.has(index)),
|
|
396
|
+
cfg,
|
|
397
|
+
model,
|
|
398
|
+
})
|
|
399
|
+
// Allow plugins to inject context or replace compaction prompt.
|
|
400
|
+
const compacting = yield* plugin.trigger(
|
|
401
|
+
"experimental.session.compacting",
|
|
402
|
+
{ sessionID: input.sessionID },
|
|
403
|
+
{ context: [], prompt: undefined },
|
|
404
|
+
)
|
|
405
|
+
const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context })
|
|
406
|
+
const msgs = structuredClone(selected.head)
|
|
407
|
+
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
|
408
|
+
const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, {
|
|
409
|
+
stripMedia: true,
|
|
410
|
+
toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS,
|
|
411
|
+
})
|
|
412
|
+
const ctx = yield* InstanceState.context
|
|
413
|
+
const msg: MessageV2.Assistant = {
|
|
414
|
+
id: MessageID.ascending(),
|
|
415
|
+
role: "assistant",
|
|
416
|
+
parentID: input.parentID,
|
|
417
|
+
sessionID: input.sessionID,
|
|
418
|
+
mode: "compaction",
|
|
419
|
+
agent: "compaction",
|
|
420
|
+
variant: userMessage.model.variant,
|
|
421
|
+
summary: true,
|
|
422
|
+
path: {
|
|
423
|
+
cwd: ctx.directory,
|
|
424
|
+
root: ctx.worktree,
|
|
425
|
+
},
|
|
426
|
+
cost: 0,
|
|
427
|
+
tokens: {
|
|
428
|
+
output: 0,
|
|
429
|
+
input: 0,
|
|
430
|
+
reasoning: 0,
|
|
431
|
+
cache: { read: 0, write: 0 },
|
|
432
|
+
},
|
|
433
|
+
modelID: model.id,
|
|
434
|
+
providerID: model.providerID,
|
|
435
|
+
time: {
|
|
436
|
+
created: Date.now(),
|
|
437
|
+
},
|
|
438
|
+
}
|
|
439
|
+
yield* session.updateMessage(msg)
|
|
440
|
+
const processor = yield* processors.create({
|
|
441
|
+
assistantMessage: msg,
|
|
442
|
+
sessionID: input.sessionID,
|
|
443
|
+
model,
|
|
444
|
+
})
|
|
445
|
+
const result = yield* processor.process({
|
|
446
|
+
user: userMessage,
|
|
447
|
+
agent,
|
|
448
|
+
sessionID: input.sessionID,
|
|
449
|
+
tools: {},
|
|
450
|
+
system: [],
|
|
451
|
+
messages: [
|
|
452
|
+
...modelMessages,
|
|
453
|
+
{
|
|
454
|
+
role: "user",
|
|
455
|
+
content: [{ type: "text", text: nextPrompt }],
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
model,
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
if (result === "compact") {
|
|
462
|
+
processor.message.error = new MessageV2.ContextOverflowError({
|
|
463
|
+
message: replay
|
|
464
|
+
? "Conversation history too large to compact - exceeds model context limit"
|
|
465
|
+
: "Session too large to compact - context exceeds model limit even after stripping media",
|
|
466
|
+
}).toObject()
|
|
467
|
+
processor.message.finish = "error"
|
|
468
|
+
yield* session.updateMessage(processor.message)
|
|
469
|
+
return "stop"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (compactionPart && selected.tail_start_id && compactionPart.tail_start_id !== selected.tail_start_id) {
|
|
473
|
+
yield* session.updatePart({
|
|
474
|
+
...compactionPart,
|
|
475
|
+
tail_start_id: selected.tail_start_id,
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (result === "continue" && input.auto) {
|
|
480
|
+
if (replay) {
|
|
481
|
+
const original = replay.info
|
|
482
|
+
const replayMsg = yield* session.updateMessage({
|
|
483
|
+
id: MessageID.ascending(),
|
|
484
|
+
role: "user",
|
|
485
|
+
sessionID: input.sessionID,
|
|
486
|
+
time: { created: Date.now() },
|
|
487
|
+
agent: original.agent,
|
|
488
|
+
model: original.model,
|
|
489
|
+
format: original.format,
|
|
490
|
+
tools: original.tools,
|
|
491
|
+
system: original.system,
|
|
492
|
+
})
|
|
493
|
+
for (const part of replay.parts) {
|
|
494
|
+
if (part.type === "compaction") continue
|
|
495
|
+
const replayPart =
|
|
496
|
+
part.type === "file" && MessageV2.isMedia(part.mime)
|
|
497
|
+
? { type: "text" as const, text: `[Attached ${part.mime}: ${part.filename ?? "file"}]` }
|
|
498
|
+
: part
|
|
499
|
+
yield* session.updatePart({
|
|
500
|
+
...replayPart,
|
|
501
|
+
id: PartID.ascending(),
|
|
502
|
+
messageID: replayMsg.id,
|
|
503
|
+
sessionID: input.sessionID,
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!replay) {
|
|
509
|
+
const info = yield* provider.getProvider(userMessage.model.providerID)
|
|
510
|
+
if (
|
|
511
|
+
(yield* plugin.trigger(
|
|
512
|
+
"experimental.compaction.autocontinue",
|
|
513
|
+
{
|
|
514
|
+
sessionID: input.sessionID,
|
|
515
|
+
agent: userMessage.agent,
|
|
516
|
+
model: yield* provider
|
|
517
|
+
.getModel(userMessage.model.providerID, userMessage.model.modelID)
|
|
518
|
+
.pipe(Effect.orDie),
|
|
519
|
+
provider: {
|
|
520
|
+
source: info.source,
|
|
521
|
+
info,
|
|
522
|
+
options: info.options,
|
|
523
|
+
},
|
|
524
|
+
message: userMessage,
|
|
525
|
+
overflow: input.overflow === true,
|
|
526
|
+
},
|
|
527
|
+
{ enabled: true },
|
|
528
|
+
)).enabled
|
|
529
|
+
) {
|
|
530
|
+
const continueMsg = yield* session.updateMessage({
|
|
531
|
+
id: MessageID.ascending(),
|
|
532
|
+
role: "user",
|
|
533
|
+
sessionID: input.sessionID,
|
|
534
|
+
time: { created: Date.now() },
|
|
535
|
+
agent: userMessage.agent,
|
|
536
|
+
model: userMessage.model,
|
|
537
|
+
})
|
|
538
|
+
const text =
|
|
539
|
+
(input.overflow
|
|
540
|
+
? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n"
|
|
541
|
+
: "") +
|
|
542
|
+
"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
|
|
543
|
+
yield* session.updatePart({
|
|
544
|
+
id: PartID.ascending(),
|
|
545
|
+
messageID: continueMsg.id,
|
|
546
|
+
sessionID: input.sessionID,
|
|
547
|
+
type: "text",
|
|
548
|
+
// Internal marker for auto-compaction followups so provider plugins
|
|
549
|
+
// can distinguish them from manual post-compaction user prompts.
|
|
550
|
+
// This is not a stable plugin contract and may change or disappear.
|
|
551
|
+
metadata: { compaction_continue: true },
|
|
552
|
+
synthetic: true,
|
|
553
|
+
text,
|
|
554
|
+
time: {
|
|
555
|
+
start: Date.now(),
|
|
556
|
+
end: Date.now(),
|
|
557
|
+
},
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (processor.message.error) return "stop"
|
|
564
|
+
if (result === "continue") {
|
|
565
|
+
const summary = summaryText(
|
|
566
|
+
(yield* session.messages({ sessionID: input.sessionID }).pipe(Effect.orDie)).find(
|
|
567
|
+
(item) => item.info.id === msg.id,
|
|
568
|
+
) ?? {
|
|
569
|
+
info: msg,
|
|
570
|
+
parts: [],
|
|
571
|
+
},
|
|
572
|
+
)
|
|
573
|
+
if (flags.experimentalEventSystem) {
|
|
574
|
+
yield* events.publish(SessionEvent.Compaction.Ended, {
|
|
575
|
+
sessionID: input.sessionID,
|
|
576
|
+
timestamp: DateTime.makeUnsafe(Date.now()),
|
|
577
|
+
text: summary ?? "",
|
|
578
|
+
include: selected.tail_start_id,
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
|
|
582
|
+
}
|
|
583
|
+
return result
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
const create = Effect.fn("SessionCompaction.create")(function* (input: {
|
|
587
|
+
sessionID: SessionID
|
|
588
|
+
agent: string
|
|
589
|
+
model: { providerID: ProviderID; modelID: ModelID }
|
|
590
|
+
auto: boolean
|
|
591
|
+
overflow?: boolean
|
|
592
|
+
}) {
|
|
593
|
+
const msg = yield* session.updateMessage({
|
|
594
|
+
id: MessageID.ascending(),
|
|
595
|
+
role: "user",
|
|
596
|
+
model: input.model,
|
|
597
|
+
sessionID: input.sessionID,
|
|
598
|
+
agent: input.agent,
|
|
599
|
+
time: { created: Date.now() },
|
|
600
|
+
})
|
|
601
|
+
yield* session.updatePart({
|
|
602
|
+
id: PartID.ascending(),
|
|
603
|
+
messageID: msg.id,
|
|
604
|
+
sessionID: msg.sessionID,
|
|
605
|
+
type: "compaction",
|
|
606
|
+
auto: input.auto,
|
|
607
|
+
overflow: input.overflow,
|
|
608
|
+
})
|
|
609
|
+
if (flags.experimentalEventSystem) {
|
|
610
|
+
yield* events.publish(SessionEvent.Compaction.Started, {
|
|
611
|
+
sessionID: input.sessionID,
|
|
612
|
+
timestamp: DateTime.makeUnsafe(Date.now()),
|
|
613
|
+
reason: input.auto ? "auto" : "manual",
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
return Service.of({
|
|
619
|
+
isOverflow,
|
|
620
|
+
prune,
|
|
621
|
+
process: processCompaction,
|
|
622
|
+
create,
|
|
623
|
+
})
|
|
624
|
+
}),
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
export const defaultLayer = Layer.suspend(() =>
|
|
628
|
+
layer.pipe(
|
|
629
|
+
Layer.provide(Provider.defaultLayer),
|
|
630
|
+
Layer.provide(Session.defaultLayer),
|
|
631
|
+
Layer.provide(SessionProcessor.defaultLayer),
|
|
632
|
+
Layer.provide(Agent.defaultLayer),
|
|
633
|
+
Layer.provide(Plugin.defaultLayer),
|
|
634
|
+
Layer.provide(Bus.layer),
|
|
635
|
+
Layer.provide(Config.defaultLayer),
|
|
636
|
+
Layer.provide(RuntimeFlags.defaultLayer),
|
|
637
|
+
Layer.provide(EventV2Bridge.defaultLayer),
|
|
638
|
+
),
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
const { runPromise } = makeRuntime(Service, defaultLayer)
|
|
642
|
+
|
|
643
|
+
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
|
|
644
|
+
return runPromise((svc) => svc.isOverflow(input))
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export async function prune(input: { sessionID: SessionID }) {
|
|
648
|
+
return runPromise((svc) => svc.prune(input))
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export * as SessionCompaction from "./compaction"
|