experimental-a2 0.16.1 → 0.17.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/CHANGELOG.md +352 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +432 -258
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +671 -581
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +656 -194
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +6 -3
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1964
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ControlReceipt } from 'experimental-a2/ai'
|
|
2
|
+
|
|
3
|
+
export function CommandReceipt({
|
|
4
|
+
receipt,
|
|
5
|
+
stopCommandIds,
|
|
6
|
+
}: {
|
|
7
|
+
receipt: ControlReceipt | null
|
|
8
|
+
stopCommandIds: readonly string[]
|
|
9
|
+
}) {
|
|
10
|
+
if (receipt?.outcome !== 'rejected') return null
|
|
11
|
+
if (
|
|
12
|
+
receipt.reason === 'stale-turn' &&
|
|
13
|
+
stopCommandIds.includes(receipt.commandId)
|
|
14
|
+
) {
|
|
15
|
+
return (
|
|
16
|
+
<p className="hint" role="status">
|
|
17
|
+
That turn had already finished.
|
|
18
|
+
</p>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<p className="error" role="alert">
|
|
23
|
+
Command rejected: {receipt.reason}. Refresh the input or turn you want to
|
|
24
|
+
change.
|
|
25
|
+
</p>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type { CompactionPartData } from 'experimental-a2/ai'
|
|
3
|
+
|
|
4
|
+
const number = new Intl.NumberFormat('en-US')
|
|
4
5
|
|
|
5
6
|
export function CompactionEvent({
|
|
6
|
-
|
|
7
|
+
data,
|
|
7
8
|
}: {
|
|
8
|
-
|
|
9
|
+
data: CompactionPartData
|
|
9
10
|
}): ReactNode {
|
|
10
|
-
const {
|
|
11
|
-
const
|
|
12
|
-
const label =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const { status, summary, inputTokens, thresholdTokens } = data
|
|
12
|
+
const running = status === 'running'
|
|
13
|
+
const label =
|
|
14
|
+
status === 'completed'
|
|
15
|
+
? 'Context compacted'
|
|
16
|
+
: running
|
|
17
|
+
? 'Compacting…'
|
|
18
|
+
: 'Compaction did not complete'
|
|
19
|
+
const hint = [
|
|
20
|
+
inputTokens === undefined || thresholdTokens === undefined
|
|
21
|
+
? undefined
|
|
22
|
+
: `${number.format(inputTokens)} tokens against a ${number.format(thresholdTokens)} token threshold`,
|
|
23
|
+
status === 'failed' || status === 'interrupted'
|
|
24
|
+
? `Generation ${status}`
|
|
25
|
+
: undefined,
|
|
26
|
+
]
|
|
27
|
+
.filter((line) => line !== undefined)
|
|
28
|
+
.join(' · ')
|
|
17
29
|
|
|
18
30
|
return (
|
|
19
31
|
<details className="agent-compaction-event">
|
|
20
32
|
<summary>
|
|
21
33
|
<span role={running ? 'status' : undefined}>{label}</span>
|
|
22
|
-
<time dateTime={event.createdAt.toISOString()}>
|
|
23
|
-
{formatActivityTime(event.createdAt)}
|
|
24
|
-
</time>
|
|
25
34
|
</summary>
|
|
26
|
-
<p className="hint">
|
|
27
|
-
|
|
28
|
-
{event.payload.throughIndex === undefined
|
|
29
|
-
? `Through message ${event.payload.throughMessageId}`
|
|
30
|
-
: `Summarized through event #${event.payload.throughIndex}`}
|
|
31
|
-
{outcome === undefined ? null : ` · Generation ${outcome}`}
|
|
32
|
-
</p>
|
|
33
|
-
{completed && event.payload.summary ? (
|
|
34
|
-
<pre>{event.payload.summary}</pre>
|
|
35
|
-
) : null}
|
|
35
|
+
{hint ? <p className="hint">{hint}</p> : null}
|
|
36
|
+
{summary ? <pre>{summary}</pre> : null}
|
|
36
37
|
</details>
|
|
37
38
|
)
|
|
38
39
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
4
|
+
import { demoAgent } from '../model'
|
|
5
|
+
import type { AgentSession } from '../session'
|
|
6
|
+
import { CompactionPanel } from './compaction-panel'
|
|
7
|
+
|
|
8
|
+
const loadHistory = vi.fn<AgentSession['loadHistory']>()
|
|
9
|
+
const push = vi.fn<AgentSession['push']>()
|
|
10
|
+
let session: AgentSession
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
loadHistory.mockReset().mockResolvedValue([])
|
|
14
|
+
push.mockReset()
|
|
15
|
+
session = {
|
|
16
|
+
state: demoAgent.reducer.initialState,
|
|
17
|
+
hydrated: true,
|
|
18
|
+
events: [],
|
|
19
|
+
index: 10,
|
|
20
|
+
connection: { status: 'live', reconnects: 0 },
|
|
21
|
+
history: { complete: false, loading: false, oldestLoaded: null },
|
|
22
|
+
loadHistory,
|
|
23
|
+
push,
|
|
24
|
+
reconnect: vi.fn<() => void>(),
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
afterEach(cleanup)
|
|
28
|
+
|
|
29
|
+
async function toggle(panel: HTMLDetailsElement, open: boolean) {
|
|
30
|
+
await act(async () => {
|
|
31
|
+
panel.open = open
|
|
32
|
+
fireEvent(panel, new Event('toggle'))
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('uses seeded settings immediately and loads diagnostic history only on open', async () => {
|
|
37
|
+
session.state = {
|
|
38
|
+
...session.state,
|
|
39
|
+
compactionSettings: { mode: 'manual', thresholdTokens: 1234 },
|
|
40
|
+
}
|
|
41
|
+
const view = render(<CompactionPanel session={session} />)
|
|
42
|
+
expect((screen.getByLabelText('Mode') as HTMLSelectElement).value).toBe(
|
|
43
|
+
'manual',
|
|
44
|
+
)
|
|
45
|
+
expect(
|
|
46
|
+
(screen.getByLabelText('Threshold in tokens') as HTMLInputElement).value,
|
|
47
|
+
).toBe('1234')
|
|
48
|
+
expect(push).not.toHaveBeenCalled()
|
|
49
|
+
expect(loadHistory).not.toHaveBeenCalled()
|
|
50
|
+
const panel =
|
|
51
|
+
view.container.querySelector<HTMLDetailsElement>('.agent-compaction')!
|
|
52
|
+
await toggle(panel, true)
|
|
53
|
+
expect(loadHistory).toHaveBeenCalledTimes(1)
|
|
54
|
+
await toggle(panel, false)
|
|
55
|
+
await toggle(panel, true)
|
|
56
|
+
expect(loadHistory).toHaveBeenCalledTimes(1)
|
|
57
|
+
session.state = {
|
|
58
|
+
...session.state,
|
|
59
|
+
compactionSettings: { mode: 'disabled' },
|
|
60
|
+
compactionSettingsId: 'new',
|
|
61
|
+
}
|
|
62
|
+
session.events = [
|
|
63
|
+
{
|
|
64
|
+
id: 'old',
|
|
65
|
+
sessionId: 'one',
|
|
66
|
+
index: 2,
|
|
67
|
+
createdAt: new Date(),
|
|
68
|
+
type: 'compaction.settings.updated',
|
|
69
|
+
payload: { mode: 'automatic' },
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
view.rerender(<CompactionPanel session={session} />)
|
|
73
|
+
expect((screen.getByLabelText('Mode') as HTMLSelectElement).value).toBe(
|
|
74
|
+
'disabled',
|
|
75
|
+
)
|
|
76
|
+
expect(push).not.toHaveBeenCalled()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('keeps diagnostic read failures local and retries only when requested', async () => {
|
|
80
|
+
loadHistory.mockRejectedValueOnce(new Error('History failed'))
|
|
81
|
+
const view = render(
|
|
82
|
+
<>
|
|
83
|
+
<input aria-label="composer" />
|
|
84
|
+
<CompactionPanel session={session} />
|
|
85
|
+
</>,
|
|
86
|
+
)
|
|
87
|
+
await toggle(view.container.querySelector('details')!, true)
|
|
88
|
+
expect(screen.getByRole('alert').textContent).toBe('History failed')
|
|
89
|
+
expect(screen.getByLabelText('composer')).toBeDefined()
|
|
90
|
+
expect((screen.getByLabelText('Mode') as HTMLSelectElement).value).toBe(
|
|
91
|
+
'automatic',
|
|
92
|
+
)
|
|
93
|
+
await act(async () =>
|
|
94
|
+
fireEvent.click(
|
|
95
|
+
screen.getByRole('button', { name: 'Load earlier events' }),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
expect(loadHistory).toHaveBeenCalledTimes(2)
|
|
99
|
+
expect(screen.queryByRole('alert')).toBeNull()
|
|
100
|
+
expect(push).not.toHaveBeenCalled()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('pushes the settings event and reports a failed save outside the form', async () => {
|
|
104
|
+
push.mockImplementationOnce(() =>
|
|
105
|
+
Object.assign(Promise.reject(new Error('Save failed')), {
|
|
106
|
+
confirmed: Promise.resolve([]),
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
const view = render(<CompactionPanel session={session} />)
|
|
110
|
+
await toggle(view.container.querySelector('details')!, true)
|
|
111
|
+
await act(async () =>
|
|
112
|
+
fireEvent.click(screen.getByRole('button', { name: 'Set to 1 token' })),
|
|
113
|
+
)
|
|
114
|
+
expect(push).toHaveBeenCalledWith({
|
|
115
|
+
type: 'compaction.settings.updated',
|
|
116
|
+
payload: { mode: 'manual', thresholdTokens: 1 },
|
|
117
|
+
})
|
|
118
|
+
expect(screen.getByRole('alert').textContent).toBe('Save failed')
|
|
119
|
+
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import type { FormEvent, ReactNode } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
4
|
+
import { useId, useRef, useState } from 'react'
|
|
5
|
+
import { formatActivityTime } from '../../components/activity-feed'
|
|
6
|
+
import type { AgentSession } from '../session'
|
|
7
7
|
import {
|
|
8
8
|
agentModel,
|
|
9
9
|
compactionSettingsSchema,
|
|
10
|
-
|
|
11
|
-
type AgentEvent,
|
|
10
|
+
type AgentState,
|
|
12
11
|
type CompactionSettings,
|
|
12
|
+
type AgentEvent,
|
|
13
13
|
} from '../model'
|
|
14
14
|
|
|
15
15
|
const number = new Intl.NumberFormat('en-US')
|
|
@@ -19,13 +19,15 @@ const tokens = (value: number | undefined): string =>
|
|
|
19
19
|
: `${number.format(value)} ${value === 1 ? 'token' : 'tokens'}`
|
|
20
20
|
|
|
21
21
|
function SettingsForm({
|
|
22
|
-
agentId,
|
|
23
22
|
settings,
|
|
24
23
|
closed,
|
|
24
|
+
saving,
|
|
25
|
+
save,
|
|
25
26
|
}: {
|
|
26
|
-
agentId: string
|
|
27
27
|
settings: CompactionSettings
|
|
28
28
|
closed: boolean
|
|
29
|
+
saving: boolean
|
|
30
|
+
save: (settings: CompactionSettings) => void
|
|
29
31
|
}): ReactNode {
|
|
30
32
|
const modeId = useId()
|
|
31
33
|
const thresholdId = useId()
|
|
@@ -33,46 +35,11 @@ function SettingsForm({
|
|
|
33
35
|
const [threshold, setThreshold] = useState(
|
|
34
36
|
settings.mode === 'manual' ? String(settings.thresholdTokens) : '',
|
|
35
37
|
)
|
|
36
|
-
const [saving, setSaving] = useState(false)
|
|
37
38
|
const [error, setError] = useState<string | null>(null)
|
|
38
|
-
const [notice, setNotice] = useState<string | null>(null)
|
|
39
|
-
const pending = useRef<AbortController | null>(null)
|
|
40
|
-
|
|
41
|
-
useEffect(() => () => pending.current?.abort(), [])
|
|
42
|
-
|
|
43
|
-
const save = async (next: CompactionSettings): Promise<void> => {
|
|
44
|
-
pending.current?.abort()
|
|
45
|
-
const controller = new AbortController()
|
|
46
|
-
pending.current = controller
|
|
47
|
-
setSaving(true)
|
|
48
|
-
setError(null)
|
|
49
|
-
setNotice(null)
|
|
50
|
-
try {
|
|
51
|
-
const response = await fetch(
|
|
52
|
-
`/agent/${encodeURIComponent(agentId)}/compaction`,
|
|
53
|
-
{
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: { 'content-type': 'application/json' },
|
|
56
|
-
body: JSON.stringify({ id: crypto.randomUUID(), settings: next }),
|
|
57
|
-
signal: controller.signal,
|
|
58
|
-
},
|
|
59
|
-
)
|
|
60
|
-
if (!response.ok)
|
|
61
|
-
throw new Error('Could not save compaction settings. Try again.')
|
|
62
|
-
setNotice('Saved. The live feed will confirm the latest settings.')
|
|
63
|
-
} catch (cause) {
|
|
64
|
-
if (!controller.signal.aborted)
|
|
65
|
-
setError(cause instanceof Error ? cause.message : String(cause))
|
|
66
|
-
} finally {
|
|
67
|
-
if (pending.current === controller) {
|
|
68
|
-
pending.current = null
|
|
69
|
-
setSaving(false)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
39
|
|
|
74
40
|
const submit = (event: FormEvent): void => {
|
|
75
41
|
event.preventDefault()
|
|
42
|
+
setError(null)
|
|
76
43
|
const parsed = compactionSettingsSchema.safeParse(
|
|
77
44
|
mode === 'manual'
|
|
78
45
|
? { mode, thresholdTokens: Number(threshold) }
|
|
@@ -82,7 +49,7 @@ function SettingsForm({
|
|
|
82
49
|
setError('Enter a positive whole-number threshold.')
|
|
83
50
|
return
|
|
84
51
|
}
|
|
85
|
-
|
|
52
|
+
save(parsed.data)
|
|
86
53
|
}
|
|
87
54
|
|
|
88
55
|
return (
|
|
@@ -124,14 +91,14 @@ function SettingsForm({
|
|
|
124
91
|
<button
|
|
125
92
|
type="button"
|
|
126
93
|
className="secondary"
|
|
127
|
-
onClick={() =>
|
|
94
|
+
onClick={() => save({ mode: 'manual', thresholdTokens: 1 })}
|
|
128
95
|
>
|
|
129
96
|
Set to 1 token
|
|
130
97
|
</button>
|
|
131
98
|
<button
|
|
132
99
|
type="button"
|
|
133
100
|
className="secondary"
|
|
134
|
-
onClick={() =>
|
|
101
|
+
onClick={() => save({ mode: 'automatic' })}
|
|
135
102
|
>
|
|
136
103
|
Reset to automatic
|
|
137
104
|
</button>
|
|
@@ -147,11 +114,6 @@ function SettingsForm({
|
|
|
147
114
|
Existing summaries remain in use. This stops future compaction.
|
|
148
115
|
</p>
|
|
149
116
|
) : null}
|
|
150
|
-
{notice ? (
|
|
151
|
-
<p role="status" className="hint">
|
|
152
|
-
{notice}
|
|
153
|
-
</p>
|
|
154
|
-
) : null}
|
|
155
117
|
{error ? (
|
|
156
118
|
<p role="alert" className="error">
|
|
157
119
|
{error}
|
|
@@ -161,17 +123,36 @@ function SettingsForm({
|
|
|
161
123
|
)
|
|
162
124
|
}
|
|
163
125
|
|
|
164
|
-
|
|
165
|
-
agentId,
|
|
126
|
+
function CompactionDetails({
|
|
166
127
|
state,
|
|
167
128
|
events,
|
|
129
|
+
complete,
|
|
130
|
+
push,
|
|
168
131
|
}: {
|
|
169
|
-
|
|
170
|
-
state: AIState
|
|
132
|
+
state: AgentState
|
|
171
133
|
events: readonly AgentEvent[]
|
|
134
|
+
complete: boolean
|
|
135
|
+
push: AgentSession['push']
|
|
172
136
|
}): ReactNode {
|
|
173
|
-
const
|
|
174
|
-
const
|
|
137
|
+
const settings = state.compactionSettings
|
|
138
|
+
const id = state.compactionSettingsId
|
|
139
|
+
// The save outlives the form, which remounts when the optimistic push lands.
|
|
140
|
+
const [saving, setSaving] = useState(false)
|
|
141
|
+
const [saveError, setSaveError] = useState<string | null>(null)
|
|
142
|
+
const save = async (next: CompactionSettings): Promise<void> => {
|
|
143
|
+
setSaving(true)
|
|
144
|
+
setSaveError(null)
|
|
145
|
+
try {
|
|
146
|
+
await push({ type: 'compaction.settings.updated', payload: next })
|
|
147
|
+
} catch (cause) {
|
|
148
|
+
setSaveError(cause instanceof Error ? cause.message : String(cause))
|
|
149
|
+
} finally {
|
|
150
|
+
setSaving(false)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const model =
|
|
154
|
+
state.activeGeneration?.model ?? state.modelChoice.model ?? agentModel
|
|
155
|
+
const metadata = state.modelMetadata[model]
|
|
175
156
|
const limits = metadata?.status === 'resolved' ? metadata.limits : undefined
|
|
176
157
|
const automaticThreshold =
|
|
177
158
|
limits === undefined ? undefined : Math.floor(limits.contextWindow * 0.75)
|
|
@@ -188,127 +169,189 @@ export function CompactionPanel({
|
|
|
188
169
|
.filter((event) => event.type === 'ai.compaction.requested')
|
|
189
170
|
.map((event) => [event.payload.generationId, event.payload]),
|
|
190
171
|
)
|
|
191
|
-
|
|
172
|
+
return (
|
|
173
|
+
<div className="compaction-controls">
|
|
174
|
+
<p className="hint">
|
|
175
|
+
Model: <code>{model}</code>. Limits are saved for this session.
|
|
176
|
+
</p>
|
|
177
|
+
<dl className="compaction-metrics">
|
|
178
|
+
<div>
|
|
179
|
+
<dt>Context window</dt>
|
|
180
|
+
<dd>{tokens(limits?.contextWindow)}</dd>
|
|
181
|
+
</div>
|
|
182
|
+
<div>
|
|
183
|
+
<dt>Model output ceiling</dt>
|
|
184
|
+
<dd>{tokens(limits?.maxOutputTokens)}</dd>
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
<dt>Automatic trigger · 75%</dt>
|
|
188
|
+
<dd>{tokens(automaticThreshold)}</dd>
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<dt>Threshold for next step</dt>
|
|
192
|
+
<dd>
|
|
193
|
+
{settings.mode === 'disabled'
|
|
194
|
+
? 'Disabled'
|
|
195
|
+
: tokens(effectiveThreshold)}
|
|
196
|
+
</dd>
|
|
197
|
+
</div>
|
|
198
|
+
<div>
|
|
199
|
+
<dt>Last loaded step input · reported</dt>
|
|
200
|
+
<dd>{tokens(lastStep?.payload.usage?.inputTokens)}</dd>
|
|
201
|
+
</div>
|
|
202
|
+
<div>
|
|
203
|
+
<dt>Last loaded step input · raw estimate</dt>
|
|
204
|
+
<dd>{tokens(lastStep?.payload.inputTokenEstimate)}</dd>
|
|
205
|
+
</div>
|
|
206
|
+
</dl>
|
|
207
|
+
<p className="hint">
|
|
208
|
+
The raw estimate excludes provider metadata. Compaction adjusts it using
|
|
209
|
+
the previous step’s reported usage when the model matches and the prompt
|
|
210
|
+
has not shrunk.
|
|
211
|
+
</p>
|
|
212
|
+
{limits === undefined ? (
|
|
213
|
+
<p className="hint">
|
|
214
|
+
{metadata?.status === 'unavailable'
|
|
215
|
+
? 'Gateway did not report limits for this model. You can set a custom threshold.'
|
|
216
|
+
: metadata?.status === 'pending'
|
|
217
|
+
? 'Model limits have been requested but are not resolved yet. The agent can continue.'
|
|
218
|
+
: 'Send a message to discover the model limits, or set a custom threshold first.'}
|
|
219
|
+
</p>
|
|
220
|
+
) : null}
|
|
221
|
+
<SettingsForm
|
|
222
|
+
key={id}
|
|
223
|
+
settings={settings}
|
|
224
|
+
closed={state.status === 'closed'}
|
|
225
|
+
saving={saving}
|
|
226
|
+
save={(next) => void save(next)}
|
|
227
|
+
/>
|
|
228
|
+
{saveError ? (
|
|
229
|
+
<p role="alert" className="error">
|
|
230
|
+
{saveError}
|
|
231
|
+
</p>
|
|
232
|
+
) : null}
|
|
233
|
+
<details className="compaction-history">
|
|
234
|
+
<summary>
|
|
235
|
+
Loaded compactions <span>{completed.length}</span>
|
|
236
|
+
</summary>
|
|
237
|
+
{completed.length === 0 ? (
|
|
238
|
+
<p className="hint">
|
|
239
|
+
{complete
|
|
240
|
+
? 'No completed compactions yet.'
|
|
241
|
+
: 'No completed compactions in the loaded history.'}
|
|
242
|
+
</p>
|
|
243
|
+
) : (
|
|
244
|
+
<ol>
|
|
245
|
+
{completed.map((event) => {
|
|
246
|
+
const trigger = requested.get(event.payload.generationId)
|
|
247
|
+
return (
|
|
248
|
+
<li key={event.id}>
|
|
249
|
+
<div className="compaction-marker">
|
|
250
|
+
<strong>Context compacted · #{event.index}</strong>
|
|
251
|
+
<time dateTime={event.createdAt.toISOString()}>
|
|
252
|
+
{formatActivityTime(event.createdAt)}
|
|
253
|
+
</time>
|
|
254
|
+
</div>
|
|
255
|
+
<p className="hint">
|
|
256
|
+
{event.payload.throughIndex === undefined
|
|
257
|
+
? `Through message ${event.payload.throughMessageId}`
|
|
258
|
+
: `Summarized through event #${event.payload.throughIndex}`}{' '}
|
|
259
|
+
· {tokens(event.payload.usage?.inputTokens)} in ·{' '}
|
|
260
|
+
{tokens(event.payload.usage?.outputTokens)} out
|
|
261
|
+
</p>
|
|
262
|
+
{trigger?.inputTokens === undefined ? null : (
|
|
263
|
+
<p className="hint">
|
|
264
|
+
Trigger: {tokens(trigger.inputTokens)} against a{' '}
|
|
265
|
+
{tokens(trigger.thresholdTokens)} threshold. Raw estimate:{' '}
|
|
266
|
+
{tokens(trigger.inputTokenEstimate)}.
|
|
267
|
+
</p>
|
|
268
|
+
)}
|
|
269
|
+
{event.payload.summary ? (
|
|
270
|
+
<details>
|
|
271
|
+
<summary>View summary</summary>
|
|
272
|
+
<pre>{event.payload.summary}</pre>
|
|
273
|
+
</details>
|
|
274
|
+
) : null}
|
|
275
|
+
</li>
|
|
276
|
+
)
|
|
277
|
+
})}
|
|
278
|
+
</ol>
|
|
279
|
+
)}
|
|
280
|
+
</details>
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
192
284
|
|
|
285
|
+
export function CompactionPanel({
|
|
286
|
+
session,
|
|
287
|
+
}: {
|
|
288
|
+
session: AgentSession
|
|
289
|
+
}): ReactNode {
|
|
290
|
+
const { state, events, history, loadHistory, push } = session
|
|
291
|
+
const opened = useRef(false)
|
|
292
|
+
const [historyError, setHistoryError] = useState<string | null>(null)
|
|
293
|
+
const loadEarlier = async (): Promise<void> => {
|
|
294
|
+
setHistoryError(null)
|
|
295
|
+
try {
|
|
296
|
+
await loadHistory()
|
|
297
|
+
} catch (error) {
|
|
298
|
+
setHistoryError(
|
|
299
|
+
error instanceof Error ? error.message : 'Could not load history.',
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
193
303
|
return (
|
|
194
|
-
<details
|
|
304
|
+
<details
|
|
305
|
+
className="agent-compaction"
|
|
306
|
+
onToggle={(event) => {
|
|
307
|
+
if (
|
|
308
|
+
event.target !== event.currentTarget ||
|
|
309
|
+
!event.currentTarget.open ||
|
|
310
|
+
opened.current
|
|
311
|
+
)
|
|
312
|
+
return
|
|
313
|
+
opened.current = true
|
|
314
|
+
if (!history.complete) void loadEarlier()
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
195
317
|
<summary>
|
|
196
318
|
<span className="compaction-chevron" aria-hidden="true">
|
|
197
319
|
›
|
|
198
320
|
</span>
|
|
199
321
|
<strong>Compaction</strong>
|
|
200
|
-
<span
|
|
201
|
-
{
|
|
322
|
+
<span
|
|
323
|
+
className={`badge${state.compaction?.status === 'running' ? ' live' : ''}`}
|
|
324
|
+
>
|
|
325
|
+
{state.compaction?.status === 'running'
|
|
202
326
|
? 'Compacting…'
|
|
203
|
-
:
|
|
327
|
+
: state.compactionSettings.mode === 'disabled'
|
|
204
328
|
? 'Disabled'
|
|
205
|
-
:
|
|
206
|
-
? `Custom · ${tokens(
|
|
329
|
+
: state.compactionSettings.mode === 'manual'
|
|
330
|
+
? `Custom · ${tokens(state.compactionSettings.thresholdTokens)}`
|
|
207
331
|
: 'Automatic'}
|
|
208
332
|
</span>
|
|
209
333
|
</summary>
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<div>
|
|
220
|
-
<dt>Model output ceiling</dt>
|
|
221
|
-
<dd>{tokens(limits?.maxOutputTokens)}</dd>
|
|
222
|
-
</div>
|
|
223
|
-
<div>
|
|
224
|
-
<dt>Automatic trigger · 75%</dt>
|
|
225
|
-
<dd>{tokens(automaticThreshold)}</dd>
|
|
226
|
-
</div>
|
|
227
|
-
<div>
|
|
228
|
-
<dt>Threshold for next step</dt>
|
|
229
|
-
<dd>
|
|
230
|
-
{settings.mode === 'disabled'
|
|
231
|
-
? 'Disabled'
|
|
232
|
-
: tokens(effectiveThreshold)}
|
|
233
|
-
</dd>
|
|
234
|
-
</div>
|
|
235
|
-
<div>
|
|
236
|
-
<dt>Last step input · reported</dt>
|
|
237
|
-
<dd>{tokens(lastStep?.payload.usage?.inputTokens)}</dd>
|
|
238
|
-
</div>
|
|
239
|
-
<div>
|
|
240
|
-
<dt>Last step input · raw estimate</dt>
|
|
241
|
-
<dd>{tokens(lastStep?.payload.inputTokenEstimate)}</dd>
|
|
242
|
-
</div>
|
|
243
|
-
</dl>
|
|
244
|
-
<p className="hint">
|
|
245
|
-
The raw estimate excludes provider metadata. Compaction adjusts it
|
|
246
|
-
using the previous step’s reported usage when the model matches and
|
|
247
|
-
the prompt has not shrunk.
|
|
334
|
+
<CompactionDetails
|
|
335
|
+
state={state}
|
|
336
|
+
events={events}
|
|
337
|
+
complete={history.complete}
|
|
338
|
+
push={push}
|
|
339
|
+
/>
|
|
340
|
+
{historyError ? (
|
|
341
|
+
<p role="alert" className="error">
|
|
342
|
+
{historyError}
|
|
248
343
|
</p>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
agentId={agentId}
|
|
261
|
-
settings={settings}
|
|
262
|
-
closed={state.status === 'closed'}
|
|
263
|
-
/>
|
|
264
|
-
<details className="compaction-history">
|
|
265
|
-
<summary>
|
|
266
|
-
Completed compactions <span>{completed.length}</span>
|
|
267
|
-
</summary>
|
|
268
|
-
{completed.length === 0 ? (
|
|
269
|
-
<p className="hint">
|
|
270
|
-
No completed compactions yet. Each completion will stay here after
|
|
271
|
-
reload.
|
|
272
|
-
</p>
|
|
273
|
-
) : (
|
|
274
|
-
<ol>
|
|
275
|
-
{completed.map((event) => {
|
|
276
|
-
const trigger = requested.get(event.payload.generationId)
|
|
277
|
-
return (
|
|
278
|
-
<li key={event.id}>
|
|
279
|
-
<div className="compaction-marker">
|
|
280
|
-
<strong>Context compacted · #{event.index}</strong>
|
|
281
|
-
<time dateTime={event.createdAt.toISOString()}>
|
|
282
|
-
{formatActivityTime(event.createdAt)}
|
|
283
|
-
</time>
|
|
284
|
-
</div>
|
|
285
|
-
<p className="hint">
|
|
286
|
-
{event.payload.throughIndex === undefined
|
|
287
|
-
? `Through message ${event.payload.throughMessageId}`
|
|
288
|
-
: `Summarized through event #${event.payload.throughIndex}`}{' '}
|
|
289
|
-
· {tokens(event.payload.usage?.inputTokens)} in ·{' '}
|
|
290
|
-
{tokens(event.payload.usage?.outputTokens)} out
|
|
291
|
-
</p>
|
|
292
|
-
{trigger?.inputTokens === undefined ? null : (
|
|
293
|
-
<p className="hint">
|
|
294
|
-
Trigger: {tokens(trigger.inputTokens)} against a{' '}
|
|
295
|
-
{tokens(trigger.thresholdTokens)} threshold. Raw
|
|
296
|
-
estimate: {tokens(trigger.inputTokenEstimate)}.
|
|
297
|
-
</p>
|
|
298
|
-
)}
|
|
299
|
-
{event.payload.summary ? (
|
|
300
|
-
<details>
|
|
301
|
-
<summary>View summary</summary>
|
|
302
|
-
<pre>{event.payload.summary}</pre>
|
|
303
|
-
</details>
|
|
304
|
-
) : null}
|
|
305
|
-
</li>
|
|
306
|
-
)
|
|
307
|
-
})}
|
|
308
|
-
</ol>
|
|
309
|
-
)}
|
|
310
|
-
</details>
|
|
311
|
-
</div>
|
|
344
|
+
) : null}
|
|
345
|
+
{!history.complete ? (
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
className="secondary"
|
|
349
|
+
disabled={history.loading}
|
|
350
|
+
onClick={() => void loadEarlier()}
|
|
351
|
+
>
|
|
352
|
+
{history.loading ? 'Loading history…' : 'Load earlier events'}
|
|
353
|
+
</button>
|
|
354
|
+
) : null}
|
|
312
355
|
</details>
|
|
313
356
|
)
|
|
314
357
|
}
|