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.
- package/CHANGELOG.md +19 -0
- package/dist/{actor-D_54lz_1.d.ts → actor-DJi3RsNu.d.ts} +2 -2
- package/dist/{actor-D_54lz_1.d.ts.map → actor-DJi3RsNu.d.ts.map} +1 -1
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-react.d.ts +1 -1
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +1 -1
- package/dist/ai-server.d.ts +12 -4
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +477 -143
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +33 -2
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +39 -7
- package/dist/ai.js.map +1 -1
- package/dist/scheduler-qstash.d.ts +1 -1
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +1 -1
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-Dkz2a84E.js → server-BeNADlCI.js} +8 -5
- package/dist/server-BeNADlCI.js.map +1 -0
- package/dist/{server-DwPrMqHB.d.ts → server-DjZZa1wr.d.ts} +8 -2
- package/dist/{server-DwPrMqHB.d.ts.map → server-DjZZa1wr.d.ts.map} +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/docs/guides/06-ai-agents.mdx +100 -18
- package/docs/reference/01-api.mdx +105 -5
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +31 -16
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +128 -0
- package/examples/playground/app/agent/compaction-settings.ts +49 -0
- package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
- package/examples/playground/app/agent/compaction-timeline.ts +198 -0
- package/examples/playground/app/agent/model.ts +47 -1
- package/examples/playground/app/agent/server.ts +3 -2
- package/examples/playground/app/globals.css +154 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-model-metadata.ts +108 -0
- package/src/ai-sdk-step.ts +5 -1
- package/src/ai-server.ts +712 -203
- package/src/ai.ts +99 -4
- package/src/server.ts +14 -3
- package/dist/server-Dkz2a84E.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-
|
|
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
|
|
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-
|
|
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
|
|
766
|
-
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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.
|
|
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`
|
|
1189
|
-
|
|
1190
|
-
|
|
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.
|
|
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
|
-
{
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
key={
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
message.
|
|
273
|
-
|
|
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 &&
|
|
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
|
+
}
|