experimental-a2 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/{actor-D_54lz_1.d.ts → actor-DJi3RsNu.d.ts} +2 -2
  3. package/dist/{actor-D_54lz_1.d.ts.map → actor-DJi3RsNu.d.ts.map} +1 -1
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-react.d.ts +1 -1
  6. package/dist/actor.d.ts +1 -1
  7. package/dist/actor.js +1 -1
  8. package/dist/ai-server.d.ts +12 -4
  9. package/dist/ai-server.d.ts.map +1 -1
  10. package/dist/ai-server.js +477 -143
  11. package/dist/ai-server.js.map +1 -1
  12. package/dist/ai.d.ts +33 -2
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +39 -7
  15. package/dist/ai.js.map +1 -1
  16. package/dist/scheduler-qstash.d.ts +1 -1
  17. package/dist/scheduler-qstash.js +1 -1
  18. package/dist/scheduler-vercel.d.ts +1 -1
  19. package/dist/scheduler-vercel.js +1 -1
  20. package/dist/{server-Dkz2a84E.js → server-BeNADlCI.js} +8 -5
  21. package/dist/server-BeNADlCI.js.map +1 -0
  22. package/dist/{server-DwPrMqHB.d.ts → server-DjZZa1wr.d.ts} +8 -2
  23. package/dist/{server-DwPrMqHB.d.ts.map → server-DjZZa1wr.d.ts.map} +1 -1
  24. package/dist/server.d.ts +2 -2
  25. package/dist/server.js +2 -2
  26. package/docs/guides/06-ai-agents.mdx +100 -18
  27. package/docs/reference/01-api.mdx +105 -5
  28. package/examples/playground/app/agent/[agentId]/agent-client.tsx +31 -16
  29. package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
  30. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
  31. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
  32. package/examples/playground/app/agent/compaction-settings.test.ts +128 -0
  33. package/examples/playground/app/agent/compaction-settings.ts +49 -0
  34. package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
  35. package/examples/playground/app/agent/compaction-timeline.ts +198 -0
  36. package/examples/playground/app/agent/model.ts +47 -1
  37. package/examples/playground/app/agent/server.ts +3 -2
  38. package/examples/playground/app/globals.css +154 -0
  39. package/examples/playground/package.json +1 -1
  40. package/package.json +1 -1
  41. package/src/ai-model-metadata.ts +108 -0
  42. package/src/ai-sdk-step.ts +5 -1
  43. package/src/ai-server.ts +712 -203
  44. package/src/ai.ts +99 -4
  45. package/src/server.ts +14 -3
  46. package/dist/server-Dkz2a84E.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"server-DwPrMqHB.d.ts","names":[],"sources":["../src/session-socket.ts","../src/server-fetch.ts","../src/scheduler-task.ts","../src/server.ts"],"mappings":"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"}
1
+ {"version":3,"file":"server-DjZZa1wr.d.ts","names":[],"sources":["../src/session-socket.ts","../src/server-fetch.ts","../src/scheduler-task.ts","../src/server.ts"],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { a as ContractEvent, c as PresenceDefs, d as PresenceSnapshot, i as Contract, l as PresenceMap, r as AppendInput, s as EventDefs, u as PresencePatch } from "./reducer-DJKWm3cp.js";
2
2
  import { a as EventCause, c as PresenceRow, d as StoreStateRead, f as StoredEvent, i as Event, l as StoreAppendResult, n as AppendEvent, o as FailAttemptResult, r as Clock, s as IdSource, t as A2Store, u as StoreClaimAvailableResult } from "./store-DtDOWLSn.js";
3
- import { A as UpgradeWebSocket, C as SchedulerAppendTask, D as A2PushEvent, E as A2Operation, O as A2PushPresence, S as ScheduledEvent, T as SchedulerTask, _ as SessionPresence, a as Handler, b as createServer, c as HandlerEntry, d as ScheduleDelay, f as ScheduleTiming, g as SessionDispatch, h as SessionAppend, i as DrainableServer, j as A2Socket, k as ServerFetchOptions, l as Lane, m as Session, n as A2Server, o as HandlerAppend, p as ServerOptions, r as AbortSpec, s as HandlerContext, t as A2Scheduler, u as LaneContext, v as SessionSchedule, w as SchedulerDrainTask, x as deliverSchedulerAppend, y as StateOptions } from "./server-DwPrMqHB.js";
4
- export { type A2Operation, type A2PushEvent, type A2PushPresence, A2Scheduler, A2Server, type A2Socket, type A2Store, AbortSpec, type AppendEvent, type AppendInput, type Clock, type Contract, type ContractEvent, DrainableServer, type Event, type EventCause, type EventDefs, type FailAttemptResult, Handler, HandlerAppend, HandlerContext, HandlerEntry, type IdSource, Lane, LaneContext, type PresenceDefs, type PresenceMap, type PresencePatch, type PresenceRow, type PresenceSnapshot, ScheduleDelay, ScheduleTiming, type ScheduledEvent, type SchedulerAppendTask, type SchedulerDrainTask, type SchedulerTask, type ServerFetchOptions, ServerOptions, Session, SessionAppend, SessionDispatch, SessionPresence, SessionSchedule, StateOptions, type StoreAppendResult, type StoreClaimAvailableResult, type StoreStateRead, type StoredEvent, type UpgradeWebSocket, createServer, deliverSchedulerAppend };
3
+ import { A as ServerFetchOptions, C as ScheduledEvent, D as A2Operation, E as SchedulerTask, M as A2Socket, O as A2PushEvent, S as handler, T as SchedulerDrainTask, _ as SessionPresence, a as Handler, b as createServer, c as HandlerEntry, d as ScheduleDelay, f as ScheduleTiming, g as SessionDispatch, h as SessionAppend, i as DrainableServer, j as UpgradeWebSocket, k as A2PushPresence, l as Lane, m as Session, n as A2Server, o as HandlerAppend, p as ServerOptions, r as AbortSpec, s as HandlerContext, t as A2Scheduler, u as LaneContext, v as SessionSchedule, w as SchedulerAppendTask, x as deliverSchedulerAppend, y as StateOptions } from "./server-DjZZa1wr.js";
4
+ export { type A2Operation, type A2PushEvent, type A2PushPresence, A2Scheduler, A2Server, type A2Socket, type A2Store, AbortSpec, type AppendEvent, type AppendInput, type Clock, type Contract, type ContractEvent, DrainableServer, type Event, type EventCause, type EventDefs, type FailAttemptResult, Handler, HandlerAppend, HandlerContext, HandlerEntry, type IdSource, Lane, LaneContext, type PresenceDefs, type PresenceMap, type PresencePatch, type PresenceRow, type PresenceSnapshot, ScheduleDelay, ScheduleTiming, type ScheduledEvent, type SchedulerAppendTask, type SchedulerDrainTask, type SchedulerTask, type ServerFetchOptions, ServerOptions, Session, SessionAppend, SessionDispatch, SessionPresence, SessionSchedule, StateOptions, type StoreAppendResult, type StoreClaimAvailableResult, type StoreStateRead, type StoredEvent, type UpgradeWebSocket, createServer, deliverSchedulerAppend, handler };
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as deliverSchedulerAppend, t as createServer } from "./server-Dkz2a84E.js";
2
- export { createServer, deliverSchedulerAppend };
1
+ import { n as deliverSchedulerAppend, r as handler, t as createServer } from "./server-BeNADlCI.js";
2
+ export { createServer, deliverSchedulerAppend, handler };
@@ -762,35 +762,111 @@ interaction that began elsewhere.
762
762
 
763
763
  ## Compact long conversations
764
764
 
765
- Compaction is optional application policy. A2 records both the decision and
766
- replacement messages, so later prompts remain explainable from history:
765
+ Compaction is automatic for supported Gateway models. A2 uses the active model
766
+ with the same instructions, tool definitions, and provider settings, and appends
767
+ an internal user message asking for a summary. This preserves the request prefix
768
+ for prompt caching when the provider has a matching cache entry.
767
769
 
768
770
  ```ts server/compacted.ts
769
- import type { UIMessage } from 'ai'
770
771
  import { createAgentServer } from 'experimental-a2/ai/server'
771
772
  import { assistant } from '../assistant'
772
773
 
773
774
  export const compactedAssistantServer = createAgentServer({
774
775
  agent: assistant,
775
776
  model: 'openai/gpt-5.6-terra',
776
- compaction: {
777
- shouldCompact: ({ messages }) => messages.length > 40,
778
- compact: async ({ messages }) => {
779
- const summary = {
780
- id: crypto.randomUUID(),
781
- role: 'user',
782
- parts: [{ type: 'text', text: 'Summary of the earlier conversation.' }],
783
- } satisfies UIMessage
784
-
785
- return [summary, ...messages.slice(-10)]
786
- },
787
- },
788
777
  })
789
778
  ```
790
779
 
791
- The callback can call another model, create a deterministic summary, or retain
792
- selected messages. A2 owns only when the result enters the log and how later
793
- generations consume it.
780
+ On first use of each Gateway model in a session, A2 includes
781
+ `ai.model.metadata.requested` in the existing generation-start append. A separate
782
+ handler fetches the public Gateway model catalog and records the selected limits
783
+ in `ai.model.metadata.resolved`. It runs outside the AI turn lane. The first model
784
+ request proceeds immediately; metadata that arrives afterward is available to
785
+ later steps. Returning to a previously used model reuses its durable metadata.
786
+ The catalog is also cached in memory for one hour across agent servers, with one
787
+ shared in-flight request and a five-second timeout. A failed lookup follows A2's
788
+ handler retry policy without failing the model generation. A model absent from
789
+ the catalog is recorded as unavailable. A pending entry means no result has been
790
+ recorded, including when the metadata handler exhausts its retry budget. A2 does
791
+ not start a fresh lookup on every turn after exhaustion. Inspect the handler
792
+ failure or use an explicit threshold in that case.
793
+
794
+ The default threshold is 75% of the context window. A larger configured
795
+ `generation.maxOutputTokens` lowers the threshold further to reserve that output
796
+ allowance. This is an input-context policy, not a new output limit. Limits and
797
+ metadata status are available in `state.modelMetadata`.
798
+
799
+ `compaction: { thresholdTokens: 80_000 }` overrides discovery with an explicit
800
+ positive safe integer. `compaction: { instructions: 'Preserve exact IDs.' }`
801
+ adds guidance to the built-in summary prompt while keeping the derived threshold.
802
+ `compaction: false` disables both compaction and metadata lookup. The `compaction`
803
+ option can also be a synchronous resolver receiving the same
804
+ `{ event, state, history, signal }` context as `model` and `instructions`. It
805
+ returns `false`, automatic options, or a custom policy once per generation.
806
+ Use this to select a session-specific threshold from already-recorded settings.
807
+ Changes after resolution apply to a later model step. Disabling compaction
808
+ preserves summaries that were already recorded. Custom provider
809
+ models and custom global SDK providers need an explicit threshold. A custom
810
+ `generate` callback keeps compaction disabled unless explicitly configured. Gateway
811
+ fallback model lists also need an explicit threshold appropriate for every model
812
+ they may use.
813
+
814
+ The input estimate starts from serialized UTF-8 bytes divided by four, including
815
+ model messages, instructions, and tool schemas. When a preceding model call has
816
+ reported token usage for the same model and context, A2 uses that measurement as
817
+ an anchor and estimates subsequent growth. It is not a provider tokenizer and
818
+ does not precisely measure new image or audio tokens. A2 performs no remote token
819
+ counting.
820
+
821
+ On a cold session without recorded metadata or an explicit threshold, automatic
822
+ compaction waits for discovery. An oversized first prompt is sent normally and
823
+ its provider error becomes a generation failure. A2 does not silently truncate
824
+ it or attempt automatic recovery from a context-limit error. Use an explicit threshold for
825
+ applications that import a large initial conversation. The estimate is a
826
+ compaction trigger, not a definitive overflow check. Provider context-limit
827
+ errors during generation or summarization remain generation failures.
828
+
829
+ Crossing the threshold adds one summary model request. Application generation
830
+ callbacks, stream transforms, and tool hooks do not run for the summary, and it
831
+ never executes local tools. Provider-executed tools, structured output, and forced
832
+ tool choices disable implicit automatic compaction; explicitly configuring
833
+ automatic compaction for those requests throws. Use a custom policy for them.
834
+ An empty summary or a summary tool call fails the generation instead of replacing
835
+ its context.
836
+
837
+ A2 records `ai.compaction.requested` before the call and
838
+ `ai.compaction.completed` after a successful summary. The completed event stores
839
+ the summary, its usage, and the exact event frontier it covers. Later model
840
+ requests receive the summary plus everything produced after that frontier,
841
+ including later tool steps in the same assistant message. Queued user messages
842
+ remain queued. Compaction waits until earlier tool calls have terminal results.
843
+ The raw log and `state.messages` remain available in full.
844
+
845
+ Custom `shouldCompact(context)` and `compact(context)` callbacks remain
846
+ available for deterministic replacement messages or application-specific
847
+ retention. Both receive the typed UI messages and `modelMessages`, the model-ready
848
+ context including any existing summary. Custom generation callbacks also receive
849
+ `modelMessages`; use it when forwarding context to a provider. A2 stores built-in
850
+ summaries separately from application-typed UI messages, so a summary does not
851
+ need your message metadata or schema.
852
+
853
+ Render compaction as activity alongside the conversation:
854
+
855
+ ```tsx app/agent/compaction-status.tsx
856
+ 'use client'
857
+ import { useSession } from './session'
858
+
859
+ export function CompactionStatus() {
860
+ const { state } = useSession()
861
+ return state.compaction?.status === 'running' ? (
862
+ <p role="status">Compacting…</p>
863
+ ) : null
864
+ }
865
+ ```
866
+
867
+ A failed or interrupted generation restores the prior compaction state. The
868
+ synthetic summary request is not a user turn or an assistant reply in
869
+ `state.messages`.
794
870
 
795
871
  ## Extend the assembly
796
872
 
@@ -900,6 +976,12 @@ one atomic durable operation. Deterministic ids make scheduling and lifecycle
900
976
  appends effectively once, and A2 marks an incomplete model attempt
901
977
  `superseded` before starting its replacement.
902
978
 
979
+ An expired claim or superseded attempt preserves its abort reason through model,
980
+ resolver, compaction-policy, and tool calls. A2 retries that attempt without
981
+ spending its failure budget. Explicit user interruption remains terminal.
982
+ Streaming tool progress belongs to an execution attempt, so a retry can produce
983
+ different preliminary output while preserving the tool-call id for idempotency.
984
+
903
985
  Provider calls and external tool side effects remain outside that transaction.
904
986
  A process can die after an external effect succeeds and before its handler
905
987
  completion commits, so recovery may run the call again. Give side-effecting
@@ -65,6 +65,64 @@ client exposes its `A2ClientError` subclass. See
65
65
 
66
66
  ## `experimental-a2/server`
67
67
 
68
+ ### `handler(entry)`
69
+
70
+ Define handlers inline in `createServer({ handlers })` for ordinary use.
71
+ Use this helper when composing an existing handler.
72
+
73
+ Accepts a handler function or an object with `handler` and optional `lane` and
74
+ `abortOn`. Returns the object shape: a function becomes `{ handler: entry }`,
75
+ and an object is returned unchanged. The original function and object types
76
+ are preserved. Use it to compose an existing handler without branching on its
77
+ shape.
78
+
79
+ ```ts server/composed-orders.ts
80
+ import * as a2 from 'experimental-a2'
81
+ import { createServer, handler, type HandlerContext } from 'experimental-a2/server'
82
+ import { z } from 'zod'
83
+
84
+ const orders = a2.contract({
85
+ name: 'composed-orders',
86
+ events: {
87
+ created: z.object({ shopId: z.string() }),
88
+ notified: z.object({ shopId: z.string() }),
89
+ },
90
+ })
91
+
92
+ const created = handler({
93
+ lane: 'orders',
94
+ handler: async (ctx: HandlerContext<typeof orders.events, 'created'>) => {
95
+ return orders.batch({ type: 'notified', payload: ctx.event.payload })
96
+ },
97
+ })
98
+
99
+ export const ordersServer = createServer({
100
+ contract: orders,
101
+ handlers: {
102
+ created: {
103
+ ...created,
104
+ handler: async (ctx) => {
105
+ const result = await created.handler(ctx)
106
+ // Your additional work goes here:
107
+ // await notifyShop(ctx.event.payload, { idempotencyKey: ctx.event.id })
108
+ return result
109
+ },
110
+ },
111
+ },
112
+ })
113
+ ```
114
+
115
+ Spreading the object preserves its `lane` and `abortOn`. Set either property
116
+ after the spread to override it. Changing a built-in AI handler's lane can
117
+ separate it from the handlers it coordinates with.
118
+
119
+ Composition runs in one handler attempt. Work after the original function
120
+ resolves still runs before its returned events commit. A thrown error fails
121
+ the composed attempt; on retry, the wrapper and original handler can run again.
122
+ For a new handler, annotate its context as above or pass an already typed
123
+ function. `handler` does not bind a handler to a contract;
124
+ `createServer({ handlers })` checks that it matches the contract.
125
+
68
126
  ### `createServer(options)`
69
127
 
70
128
  ```ts
@@ -1091,7 +1149,7 @@ superseded owner cannot alter the projection. `terminalRequestIds` and
1091
1149
  and supersession fences across snapshots and recovery. They are optional
1092
1150
  snapshot-compatible fields with the shapes `Record<string, true>` and
1093
1151
  `Record<string, 'completed' | 'failed' | 'interrupted' | 'superseded'>`.
1094
- Extension events are ignored. The default reducer name is `a2.ai.state.v8`.
1152
+ Extension events are ignored. The default reducer name is `a2.ai.state.v10`.
1095
1153
 
1096
1154
  ### `deriveUIMessages(history)` and `reduceAIState(state, event)`
1097
1155
 
@@ -1185,9 +1243,46 @@ support wait for their provider result instead of a local executor. An
1185
1243
  authenticated provider callback appends the terminal `ai.tool.result` through
1186
1244
  the trusted server session API; the browser push allowlist rejects it.
1187
1245
 
1188
- `compaction` has `shouldCompact(context)` and `compact(context)` callbacks.
1189
- When selected, both the request and the replacement messages enter the log.
1190
- `progress` controls durable batching with `maxChunks` and `maxDelayMs`.
1246
+ `compaction` defaults to automatic for supported Gateway requests. It accepts
1247
+ `false` to disable compaction and discovery, `{ thresholdTokens?, instructions? }`
1248
+ to override automatic behavior, or the existing custom
1249
+ `{ shouldCompact(context), compact(context) }` policy. It also accepts a
1250
+ resolver `(context: AgentResolverContext) => CompactionOptions`.
1251
+ The resolver runs synchronously once per generation and must return a valid policy
1252
+ or `false`. Custom `shouldCompact` and `compact` callbacks can still be asynchronous.
1253
+ Static options are validated at construction; resolved options are validated
1254
+ before that generation starts. Each session uses its own resolved value. `thresholdTokens` is an
1255
+ optional positive safe integer. `instructions` adds to A2's internal summary
1256
+ prompt without replacing the agent's instructions.
1257
+
1258
+ Without an explicit threshold, the first generation batches a metadata request
1259
+ into its existing start write. An independent handler reads the public Gateway
1260
+ catalog and records that session/model's limits. It does not delay model dispatch.
1261
+ The default threshold is 75% of the context window, lowered for a larger configured
1262
+ output allowance. A pending or unavailable lookup leaves the threshold unknown;
1263
+ the first request proceeds, and provider context-limit errors fail normally.
1264
+ `state.modelMetadata` retains each model's pending, resolved, or unavailable state.
1265
+ Pending also covers exhausted handler retries; no new request is added per turn.
1266
+ The catalog has a shared one-hour process cache and a five-second fetch timeout.
1267
+
1268
+ Direct/custom providers, fallback model lists, and custom generators need explicit
1269
+ compaction configuration. Provider-executed tools, structured output, and forced
1270
+ tool choices skip implicit compaction and reject explicit automatic policies.
1271
+ Custom policies remain available for these configurations.
1272
+
1273
+ The input estimate includes messages, tools, and instructions, and uses prior
1274
+ reported input usage to anchor later growth where applicable. It is not a provider
1275
+ tokenizer. Completed generations record `inputTokenEstimate` beside usage. The
1276
+ built-in summary adds one model request and preserves the request prefix for
1277
+ caching. It does not invoke application generation callbacks, transforms, or tool
1278
+ hooks. Both custom policy callbacks and `generate` receive `modelMessages`, the
1279
+ model-ready prompt including summaries, alongside typed UI `messages`.
1280
+
1281
+ Built-in summaries remain separate from application-typed messages. Render
1282
+ `state.compaction?.status === 'running'` as current activity, and the durable
1283
+ `ai.compaction.*` events as historical timeline markers. The conversation in
1284
+ `state.messages` remains intact. `progress` controls durable batching with
1285
+ `maxChunks` and `maxDelayMs`.
1191
1286
 
1192
1287
  This entry point is server-only and resolves to a throwing browser stub.
1193
1288
 
@@ -1205,7 +1300,12 @@ it into `createServer({ handlers })` beside application handlers when you need
1205
1300
  a custom assembly. The table handles input facts, generation requests, model
1206
1301
  step completion, tool calls, approval responses, and terminal tool results.
1207
1302
  Application handlers spread later can deliberately replace a built-in
1208
- handler. A custom assembly owns its browser ingress policy. Use
1303
+ handler. Use `handler` from `experimental-a2/server` to normalize an
1304
+ existing entry before wrapping its `handler` and spreading its execution
1305
+ policy. Entries in this table's type are optional; narrow an entry before
1306
+ passing it to `handler`.
1307
+
1308
+ A custom assembly owns its browser ingress policy. Use
1209
1309
  `server.fetch(request, { authorize })` to reject server-authored AI event names
1210
1310
  before they reach the server. `createAgentServer()` installs the built-in
1211
1311
  browser allowlist automatically.
@@ -10,6 +10,9 @@ import { inputs } from 'experimental-a2/ai'
10
10
  import { ActivityFeed } from '@/app/components/activity-feed'
11
11
  import { ConnectionPill } from '@/app/components/connection-pill'
12
12
  import { useSession } from '../session'
13
+ import { compactionTimeline } from '../compaction-timeline'
14
+ import { CompactionEvent } from './compaction-event'
15
+ import { CompactionPanel } from './compaction-panel'
13
16
 
14
17
  type MessagePart = UIMessage['parts'][number]
15
18
  type ToolPartView = {
@@ -169,10 +172,12 @@ export function AgentClient({ agentId }: { agentId: string }): ReactNode {
169
172
  const [responding, setResponding] = useState(false)
170
173
 
171
174
  const generating = state.status === 'generating'
175
+ const compacting = state.compaction?.status === 'running'
172
176
  const canSend = state.status === 'idle' || state.status === 'failed'
177
+ const timeline = compactionTimeline({ state, events })
173
178
 
174
179
  useEffect(() => {
175
- if (canSend) promptRef.current?.focus()
180
+ if (canSend) promptRef.current?.focus({ preventScroll: true })
176
181
  }, [canSend])
177
182
 
178
183
  const send = async (event: FormEvent): Promise<void> => {
@@ -250,32 +255,43 @@ export function AgentClient({ agentId }: { agentId: string }): ReactNode {
250
255
  state.status === 'waiting' ? ' waiting' : ''
251
256
  }${state.status === 'failed' ? ' cut' : ''}`}
252
257
  >
253
- {STATUS_LABEL[state.status]}
258
+ {compacting ? 'compacting…' : STATUS_LABEL[state.status]}
254
259
  </span>
255
260
  </header>
256
261
 
262
+ <CompactionPanel agentId={agentId} state={state} events={events} />
263
+
257
264
  <section className="agent-conversation" aria-label="Conversation">
258
265
  <div className="agent-transcript" aria-live="polite">
259
- {state.messages.length === 0 ? (
266
+ {timeline.length === 0 ? (
260
267
  <p className="agent-empty">
261
268
  Ask the agent to investigate an incident, run a Bash command, or
262
269
  set a reminder for itself. Its tools and decisions will appear
263
270
  here as they enter the log.
264
271
  </p>
265
272
  ) : (
266
- state.messages.map((message) => (
267
- <AgentMessage
268
- key={message.id}
269
- message={message}
270
- thinking={
271
- generating &&
272
- message.role === 'assistant' &&
273
- !hasVisibleParts(message)
274
- }
275
- />
276
- ))
273
+ timeline.map((entry) =>
274
+ entry.type === 'compaction' ? (
275
+ <CompactionEvent key={entry.id} entry={entry} />
276
+ ) : (
277
+ <AgentMessage
278
+ key={entry.id}
279
+ message={entry.message}
280
+ thinking={
281
+ generating &&
282
+ !compacting &&
283
+ entry.lastSegment &&
284
+ entry.message.id ===
285
+ state.activeGeneration?.responseMessageId &&
286
+ !hasVisibleParts(entry.message)
287
+ }
288
+ />
289
+ ),
290
+ )
277
291
  )}
278
- {generating && state.messages.at(-1)?.role === 'user' ? (
292
+ {generating &&
293
+ !compacting &&
294
+ state.messages.at(-1)?.role === 'user' ? (
279
295
  <div className="agent-message assistant">
280
296
  <span className="agent-role">Agent</span>
281
297
  <div>
@@ -340,7 +356,6 @@ export function AgentClient({ agentId }: { agentId: string }): ReactNode {
340
356
  Message
341
357
  <textarea
342
358
  ref={promptRef}
343
- autoFocus
344
359
  rows={3}
345
360
  value={prompt}
346
361
  onChange={(event) => setPrompt(event.target.value)}
@@ -0,0 +1,14 @@
1
+ import { updateCompactionSettings } from '../../compaction-settings'
2
+ import { agentServer } from '../../server'
3
+
4
+ export async function POST(
5
+ request: Request,
6
+ context: { params: Promise<{ agentId: string }> },
7
+ ): Promise<Response> {
8
+ const { agentId } = await context.params
9
+ return updateCompactionSettings({
10
+ request,
11
+ sessionId: agentId,
12
+ append: (event) => agentServer.session(agentId).append(event),
13
+ })
14
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReactNode } from 'react'
2
+ import { formatActivityTime } from '@/app/components/activity-feed'
3
+ import type { CompactionEntry } from '../compaction-timeline'
4
+
5
+ export function CompactionEvent({
6
+ entry,
7
+ }: {
8
+ entry: CompactionEntry
9
+ }): ReactNode {
10
+ const { event, running, outcome } = entry
11
+ const completed = event.type === 'ai.compaction.completed'
12
+ const label = completed
13
+ ? 'Context compacted'
14
+ : running
15
+ ? 'Compacting…'
16
+ : 'Compaction did not complete'
17
+
18
+ return (
19
+ <details className="agent-compaction-event">
20
+ <summary>
21
+ <span role={running ? 'status' : undefined}>{label}</span>
22
+ <time dateTime={event.createdAt.toISOString()}>
23
+ {formatActivityTime(event.createdAt)}
24
+ </time>
25
+ </summary>
26
+ <p className="hint">
27
+ Event #{event.index} ·{' '}
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}
36
+ </details>
37
+ )
38
+ }