experimental-a2 0.14.1 → 0.16.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 +81 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/{actor-BfQSE0KC.d.ts → actor-ohPC-81x.d.ts} +5 -5
- package/dist/{actor-BfQSE0KC.d.ts.map → actor-ohPC-81x.d.ts.map} +1 -1
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +1 -1
- package/dist/ai-CrLYwNEx.js +2431 -0
- package/dist/ai-CrLYwNEx.js.map +1 -0
- package/dist/{ai-Cai-lCbj.d.ts → ai-HJ9fHfYI.d.ts} +72 -13
- package/dist/ai-HJ9fHfYI.d.ts.map +1 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +755 -290
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1999
- package/dist/client-BYzHjkwU.js.map +1 -1
- package/dist/{client-BAEABRZB.d.ts → client-CzyacQpJ.d.ts} +9 -9
- package/dist/client-CzyacQpJ.d.ts.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/idempotent-id-BRJVeylj.js +13 -0
- package/dist/idempotent-id-BRJVeylj.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -1
- package/dist/otel.d.ts +1 -1
- package/dist/react.d.ts +13 -13
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js.map +1 -1
- package/dist/reducer-DEMjEY_O.js.map +1 -1
- package/dist/{reducer-BcS9VDKC.d.ts → reducer-otzHjuJj.d.ts} +3 -3
- package/dist/{reducer-BcS9VDKC.d.ts.map → reducer-otzHjuJj.d.ts.map} +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +1 -1
- package/dist/{server-CjJSGcF7.js → server-BD5ckxZb.js} +39 -26
- package/dist/server-BD5ckxZb.js.map +1 -0
- package/dist/{server-Bp5Nd1pF.d.ts → server-DbD7IJVK.d.ts} +5 -5
- package/dist/{server-Bp5Nd1pF.d.ts.map → server-DbD7IJVK.d.ts.map} +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/{store-D_yhNdPz.d.ts → store-DyZM6fS5.d.ts} +3 -2
- package/dist/{store-D_yhNdPz.d.ts.map → store-DyZM6fS5.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +1 -1
- package/dist/{store-redis-notify-D2EI6gwX.js → store-redis-notify-BVLUfI3j.js} +14 -13
- package/dist/store-redis-notify-BVLUfI3j.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/{telemetry-CpeclqB2.d.ts → telemetry-B5jzpy6y.d.ts} +2 -2
- package/dist/telemetry-B5jzpy6y.d.ts.map +1 -0
- package/docs/concepts/04-state.mdx +9 -4
- package/docs/guides/06-ai-agents.mdx +134 -43
- package/docs/reference/01-api.mdx +129 -27
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +20 -28
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +123 -0
- package/examples/playground/app/agent/[agentId]/agent-queue.tsx +37 -43
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +44 -24
- package/examples/playground/app/agent/compaction-timeline.test.ts +47 -7
- package/examples/playground/app/agent/compaction-timeline.ts +13 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -5
- package/examples/playground/app/chat/[chatId]/session.ts +17 -2
- package/examples/playground/app/chat/model.test.ts +4 -4
- package/examples/playground/app/chat/model.ts +18 -6
- package/examples/playground/app/chat/server.ts +6 -3
- package/examples/playground/package.json +3 -1
- package/package.json +1 -1
- package/src/ai-client-state.ts +0 -2
- package/src/ai-context-schema.ts +296 -0
- package/src/ai-context.ts +680 -0
- package/src/ai-control-server.ts +160 -131
- package/src/ai-control-state.ts +6 -0
- package/src/ai-control.ts +6 -7
- package/src/ai-coordinator.ts +92 -38
- package/src/ai-id.ts +6 -0
- package/src/ai-input-tokens.ts +75 -0
- package/src/ai-message-projection.ts +348 -0
- package/src/ai-server.ts +232 -334
- package/src/ai-stored-state.ts +484 -0
- package/src/ai.ts +252 -418
- package/src/client.ts +21 -15
- package/src/idempotent-id.ts +11 -0
- package/src/index.ts +1 -0
- package/src/react.ts +25 -19
- package/src/reducer.ts +2 -1
- package/src/server.ts +73 -56
- package/src/store-redis-core.ts +15 -17
- package/src/store.ts +1 -0
- package/src/telemetry.ts +2 -1
- package/dist/ai-Cai-lCbj.d.ts.map +0 -1
- package/dist/ai-control-CcD4hh3y.js +0 -119
- package/dist/ai-control-CcD4hh3y.js.map +0 -1
- package/dist/ai.js.map +0 -1
- package/dist/client-BAEABRZB.d.ts.map +0 -1
- package/dist/server-CjJSGcF7.js.map +0 -1
- package/dist/store-redis-notify-D2EI6gwX.js.map +0 -1
- package/dist/telemetry-CpeclqB2.d.ts.map +0 -1
package/src/client.ts
CHANGED
|
@@ -164,10 +164,11 @@ export type SessionClient<
|
|
|
164
164
|
D extends EventDefs,
|
|
165
165
|
S,
|
|
166
166
|
P extends PresenceDefs = Record<never, never>,
|
|
167
|
+
V = S,
|
|
167
168
|
> = WithPresence<P, SessionClientPresence<P>> & {
|
|
168
169
|
readonly sessionId: string
|
|
169
170
|
subscribe(listener: () => void): () => void
|
|
170
|
-
getSnapshot(): SessionSnapshot<D,
|
|
171
|
+
getSnapshot(): SessionSnapshot<D, V, P>
|
|
171
172
|
/**
|
|
172
173
|
* Optimistic append: validates locally against the reducer's event
|
|
173
174
|
* schemas (instant `INVALID_PAYLOAD`, no flicker), applies to the
|
|
@@ -224,11 +225,12 @@ export type A2Client<
|
|
|
224
225
|
D extends EventDefs,
|
|
225
226
|
S,
|
|
226
227
|
P extends PresenceDefs = Record<never, never>,
|
|
228
|
+
V = S,
|
|
227
229
|
> = {
|
|
228
230
|
session(
|
|
229
231
|
sessionId: string,
|
|
230
232
|
options?: SessionOptions<D, S>,
|
|
231
|
-
): SessionClient<D, S, P>
|
|
233
|
+
): SessionClient<D, S, P, V>
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
/**
|
|
@@ -275,8 +277,9 @@ export type CreateClientOptions<
|
|
|
275
277
|
D extends EventDefs,
|
|
276
278
|
S,
|
|
277
279
|
P extends PresenceDefs = Record<never, never>,
|
|
280
|
+
V = S,
|
|
278
281
|
> = {
|
|
279
|
-
reducer: Reducer<D, S, P>
|
|
282
|
+
reducer: Reducer<D, S, P, V>
|
|
280
283
|
/** The route (or routes) the client speaks — see `ClientApi`. */
|
|
281
284
|
api: ClientApi
|
|
282
285
|
/** Delay the next connection attempt in milliseconds, or pause with false. */
|
|
@@ -1264,13 +1267,14 @@ type SessionRuntime<
|
|
|
1264
1267
|
D extends EventDefs,
|
|
1265
1268
|
S,
|
|
1266
1269
|
P extends PresenceDefs,
|
|
1267
|
-
|
|
1270
|
+
V,
|
|
1271
|
+
> = SessionClient<D, S, P, V> & {
|
|
1268
1272
|
hydrate(options?: SessionOptions<D, S>): void
|
|
1269
1273
|
canEvict(): boolean
|
|
1270
1274
|
}
|
|
1271
1275
|
|
|
1272
|
-
type CachedSession<D extends EventDefs, S, P extends PresenceDefs> = {
|
|
1273
|
-
runtime: SessionRuntime<D, S, P>
|
|
1276
|
+
type CachedSession<D extends EventDefs, S, P extends PresenceDefs, V> = {
|
|
1277
|
+
runtime: SessionRuntime<D, S, P, V>
|
|
1274
1278
|
timer?: ReturnType<typeof setTimeout>
|
|
1275
1279
|
}
|
|
1276
1280
|
|
|
@@ -1278,7 +1282,8 @@ export function createClient<
|
|
|
1278
1282
|
D extends EventDefs,
|
|
1279
1283
|
S,
|
|
1280
1284
|
P extends PresenceDefs = Record<never, never>,
|
|
1281
|
-
|
|
1285
|
+
V = S,
|
|
1286
|
+
>(options: CreateClientOptions<D, S, P, V>): A2Client<D, S, P, V> {
|
|
1282
1287
|
const { reducer, api } = options
|
|
1283
1288
|
const presenceDefs: Readonly<PresenceDefs> = reducer.presence
|
|
1284
1289
|
const declaresPresence = Object.keys(presenceDefs).length > 0
|
|
@@ -1303,7 +1308,7 @@ export function createClient<
|
|
|
1303
1308
|
if (Number.isNaN(gcTime) || gcTime < 0) {
|
|
1304
1309
|
throw new RangeError('gcTime must be a non-negative number or Infinity')
|
|
1305
1310
|
}
|
|
1306
|
-
const sessions = new Map<string, CachedSession<D, S, P>>()
|
|
1311
|
+
const sessions = new Map<string, CachedSession<D, S, P, V>>()
|
|
1307
1312
|
|
|
1308
1313
|
const scheduleGc = (sessionId: string, delay = gcTime): void => {
|
|
1309
1314
|
const entry = sessions.get(sessionId)
|
|
@@ -1331,7 +1336,7 @@ export function createClient<
|
|
|
1331
1336
|
const makeSession = (
|
|
1332
1337
|
sessionId: string,
|
|
1333
1338
|
sessionOptions?: SessionOptions<D, S>,
|
|
1334
|
-
): SessionRuntime<D, S, P> => {
|
|
1339
|
+
): SessionRuntime<D, S, P, V> => {
|
|
1335
1340
|
// ── server truth ─────────────────────────────────────────────
|
|
1336
1341
|
let stateIndex = sessionOptions?.initialIndex ?? 0
|
|
1337
1342
|
let streamIndex =
|
|
@@ -1373,7 +1378,7 @@ export function createClient<
|
|
|
1373
1378
|
|
|
1374
1379
|
// ── store plumbing ───────────────────────────────────────────
|
|
1375
1380
|
const listeners = new Set<() => void>()
|
|
1376
|
-
let snapshot: SessionSnapshot<D,
|
|
1381
|
+
let snapshot: SessionSnapshot<D, V, P> | null = null
|
|
1377
1382
|
let status: ConnectionStatus = 'idle'
|
|
1378
1383
|
let reconnects = 0
|
|
1379
1384
|
let lastError: A2ClientError | null = null
|
|
@@ -1476,7 +1481,7 @@ export function createClient<
|
|
|
1476
1481
|
}
|
|
1477
1482
|
}
|
|
1478
1483
|
|
|
1479
|
-
const buildSnapshot = (): SessionSnapshot<D,
|
|
1484
|
+
const buildSnapshot = (): SessionSnapshot<D, V, P> => {
|
|
1480
1485
|
const overlay = overlayEvents()
|
|
1481
1486
|
let state = foldedState
|
|
1482
1487
|
for (const event of overlay) {
|
|
@@ -1496,7 +1501,8 @@ export function createClient<
|
|
|
1496
1501
|
return true
|
|
1497
1502
|
})
|
|
1498
1503
|
return {
|
|
1499
|
-
state:
|
|
1504
|
+
state:
|
|
1505
|
+
reducer[clientStateProjection]?.(state) ?? (state as unknown as V),
|
|
1500
1506
|
events: [...serverEvents, ...displayedOverlay],
|
|
1501
1507
|
index: stateIndex,
|
|
1502
1508
|
history: historyState(),
|
|
@@ -1504,7 +1510,7 @@ export function createClient<
|
|
|
1504
1510
|
...(declaresPresence
|
|
1505
1511
|
? { presence: presenceState as PresenceMap<P> }
|
|
1506
1512
|
: {}),
|
|
1507
|
-
} as SessionSnapshot<D,
|
|
1513
|
+
} as SessionSnapshot<D, V, P>
|
|
1508
1514
|
}
|
|
1509
1515
|
|
|
1510
1516
|
// ── presence ─────────────────────────────────────────────────
|
|
@@ -2167,7 +2173,7 @@ export function createClient<
|
|
|
2167
2173
|
previous?.abort()
|
|
2168
2174
|
}
|
|
2169
2175
|
|
|
2170
|
-
const runtime: SessionRuntime<D, S, P> = {
|
|
2176
|
+
const runtime: SessionRuntime<D, S, P, V> = {
|
|
2171
2177
|
sessionId,
|
|
2172
2178
|
// Only on presence-declaring contracts — the type surface
|
|
2173
2179
|
// promises no runtime-inert members (a2-api.md §12).
|
|
@@ -2271,7 +2277,7 @@ export function createClient<
|
|
|
2271
2277
|
presenceTimer === undefined
|
|
2272
2278
|
)
|
|
2273
2279
|
},
|
|
2274
|
-
} as SessionRuntime<D, S, P>
|
|
2280
|
+
} as SessionRuntime<D, S, P, V>
|
|
2275
2281
|
|
|
2276
2282
|
return runtime
|
|
2277
2283
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export async function idempotentId(...parts: string[]): Promise<string> {
|
|
2
|
+
const digest = await crypto.subtle.digest(
|
|
3
|
+
'SHA-256',
|
|
4
|
+
new TextEncoder().encode(JSON.stringify(parts)),
|
|
5
|
+
)
|
|
6
|
+
const bytes = new Uint8Array(digest).slice(0, 16)
|
|
7
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x80
|
|
8
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80
|
|
9
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')
|
|
10
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
|
|
11
|
+
}
|
package/src/index.ts
CHANGED
package/src/react.ts
CHANGED
|
@@ -153,38 +153,43 @@ export type A2React<
|
|
|
153
153
|
D extends EventDefs,
|
|
154
154
|
S,
|
|
155
155
|
P extends PresenceDefs = Record<never, never>,
|
|
156
|
+
V = S,
|
|
156
157
|
> = {
|
|
157
158
|
SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement
|
|
158
|
-
useSession: () => UseSessionResult<D,
|
|
159
|
+
useSession: () => UseSessionResult<D, V, P>
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
export type BoundA2React<
|
|
162
163
|
D extends EventDefs,
|
|
163
164
|
S,
|
|
164
165
|
P extends PresenceDefs = Record<never, never>,
|
|
166
|
+
V = S,
|
|
165
167
|
> = {
|
|
166
168
|
SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement
|
|
167
|
-
useSession: () => UseSessionResult<D,
|
|
169
|
+
useSession: () => UseSessionResult<D, V, P>
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
export function createReact<
|
|
171
173
|
D extends EventDefs,
|
|
172
174
|
S,
|
|
173
175
|
P extends PresenceDefs = Record<never, never>,
|
|
174
|
-
|
|
176
|
+
V = S,
|
|
177
|
+
>(options: { client: A2Client<D, S, P, V> }): BoundA2React<D, S, P, V>
|
|
175
178
|
export function createReact<
|
|
176
179
|
D extends EventDefs,
|
|
177
180
|
S,
|
|
178
181
|
P extends PresenceDefs = Record<never, never>,
|
|
179
|
-
|
|
182
|
+
V = S,
|
|
183
|
+
>(options: { reducer: Reducer<D, S, P, V> }): A2React<D, S, P, V>
|
|
180
184
|
export function createReact<
|
|
181
185
|
D extends EventDefs,
|
|
182
186
|
S,
|
|
183
187
|
P extends PresenceDefs = Record<never, never>,
|
|
188
|
+
V = S,
|
|
184
189
|
>(
|
|
185
|
-
options: { client: A2Client<D, S, P> } | { reducer: Reducer<D, S, P> },
|
|
186
|
-
): BoundA2React<D, S, P> | A2React<D, S, P> {
|
|
187
|
-
const Context = createContext<SessionClient<D, S, P> | null>(null)
|
|
190
|
+
options: { client: A2Client<D, S, P, V> } | { reducer: Reducer<D, S, P, V> },
|
|
191
|
+
): BoundA2React<D, S, P, V> | A2React<D, S, P, V> {
|
|
192
|
+
const Context = createContext<SessionClient<D, S, P, V> | null>(null)
|
|
188
193
|
const resolveSession: (
|
|
189
194
|
sessionId: string,
|
|
190
195
|
api: string | undefined,
|
|
@@ -192,7 +197,7 @@ export function createReact<
|
|
|
192
197
|
initialIndex: number,
|
|
193
198
|
initialEvents: ContractEvent<D>[] | undefined,
|
|
194
199
|
participant: string | undefined,
|
|
195
|
-
) => SessionClient<D, S, P> =
|
|
200
|
+
) => SessionClient<D, S, P, V> =
|
|
196
201
|
'client' in options
|
|
197
202
|
? (
|
|
198
203
|
sessionId,
|
|
@@ -209,7 +214,7 @@ export function createReact<
|
|
|
209
214
|
...(participant === undefined ? {} : { participant }),
|
|
210
215
|
})
|
|
211
216
|
: (() => {
|
|
212
|
-
const clients = new Map<string, A2Client<D, S, P>>()
|
|
217
|
+
const clients = new Map<string, A2Client<D, S, P, V>>()
|
|
213
218
|
return (
|
|
214
219
|
sessionId: string,
|
|
215
220
|
api: string | undefined,
|
|
@@ -264,7 +269,7 @@ export function createReact<
|
|
|
264
269
|
return createElement(Context.Provider, { value: client }, props.children)
|
|
265
270
|
}
|
|
266
271
|
|
|
267
|
-
function useBoundSession(): UseSessionResult<D,
|
|
272
|
+
function useBoundSession(): UseSessionResult<D, V, P> {
|
|
268
273
|
const client = useContext(Context)
|
|
269
274
|
if (!client) {
|
|
270
275
|
throw new Error(
|
|
@@ -288,8 +293,8 @@ export function createReact<
|
|
|
288
293
|
* stream — connecting an unhydrated session would replay the whole
|
|
289
294
|
* log, and the library never picks the expensive path silently.
|
|
290
295
|
*/
|
|
291
|
-
function useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs>(
|
|
292
|
-
session: SessionClient<D, S, P>,
|
|
296
|
+
function useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs, V>(
|
|
297
|
+
session: SessionClient<D, S, P, V>,
|
|
293
298
|
live: boolean,
|
|
294
299
|
): void {
|
|
295
300
|
useEffect(() => {
|
|
@@ -298,10 +303,10 @@ function useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs>(
|
|
|
298
303
|
}, [session, live])
|
|
299
304
|
}
|
|
300
305
|
|
|
301
|
-
function useSessionResult<D extends EventDefs, S, P extends PresenceDefs>(
|
|
302
|
-
session: SessionClient<D, S, P>,
|
|
303
|
-
): UseSessionResult<D,
|
|
304
|
-
const snapshot: SessionSnapshot<D,
|
|
306
|
+
function useSessionResult<D extends EventDefs, S, P extends PresenceDefs, V>(
|
|
307
|
+
session: SessionClient<D, S, P, V>,
|
|
308
|
+
): UseSessionResult<D, V, P> {
|
|
309
|
+
const snapshot: SessionSnapshot<D, V, P> = useSyncExternalStore(
|
|
305
310
|
session.subscribe,
|
|
306
311
|
session.getSnapshot,
|
|
307
312
|
session.getSnapshot,
|
|
@@ -322,7 +327,7 @@ function useSessionResult<D extends EventDefs, S, P extends PresenceDefs>(
|
|
|
322
327
|
loadHistory: session.loadHistory,
|
|
323
328
|
history: snapshot.history,
|
|
324
329
|
...(setPresence && presence ? { presence, setPresence } : {}),
|
|
325
|
-
} as UseSessionResult<D,
|
|
330
|
+
} as UseSessionResult<D, V, P>
|
|
326
331
|
}, [snapshot, session])
|
|
327
332
|
}
|
|
328
333
|
|
|
@@ -340,11 +345,12 @@ export function useSession<
|
|
|
340
345
|
D extends EventDefs,
|
|
341
346
|
S,
|
|
342
347
|
P extends PresenceDefs = Record<never, never>,
|
|
348
|
+
V = S,
|
|
343
349
|
>(
|
|
344
|
-
client: A2Client<D, S, P>,
|
|
350
|
+
client: A2Client<D, S, P, V>,
|
|
345
351
|
sessionId: string,
|
|
346
352
|
options?: UseSessionOptions<S>,
|
|
347
|
-
): UseSessionResult<D,
|
|
353
|
+
): UseSessionResult<D, V, P> {
|
|
348
354
|
const { participant, hydrate } = options ?? {}
|
|
349
355
|
const session = useMemo(
|
|
350
356
|
() =>
|
package/src/reducer.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type Reducer<
|
|
|
20
20
|
D extends EventDefs = EventDefs,
|
|
21
21
|
S = unknown,
|
|
22
22
|
P extends PresenceDefs = Record<never, never>,
|
|
23
|
+
V = S,
|
|
23
24
|
> = {
|
|
24
25
|
/** Identity and cache key. Rename when the fold's logic changes. */
|
|
25
26
|
readonly name: string
|
|
@@ -34,7 +35,7 @@ export type Reducer<
|
|
|
34
35
|
readonly fold: (state: S, event: ContractEvent<D>) => S
|
|
35
36
|
readonly initialState: S
|
|
36
37
|
/** Internal read-side projection. Never used as a fold input or persisted. */
|
|
37
|
-
readonly [clientStateProjection]?: (state: S) =>
|
|
38
|
+
readonly [clientStateProjection]?: (state: S) => V
|
|
38
39
|
/**
|
|
39
40
|
* When present: `initialState` was validated against it at
|
|
40
41
|
* definition, and any cached fold a2 rehydrates (server snapshots,
|
package/src/server.ts
CHANGED
|
@@ -183,8 +183,8 @@ export type Session<
|
|
|
183
183
|
append: Append
|
|
184
184
|
schedule: SessionSchedule<D>
|
|
185
185
|
history(options?: { gte?: number; lte?: number }): Promise<ContractEvent<D>[]>
|
|
186
|
-
state<S>(
|
|
187
|
-
reducer: Reducer<D, S>,
|
|
186
|
+
state<S, V = S>(
|
|
187
|
+
reducer: Reducer<D, S, Record<never, never>, V>,
|
|
188
188
|
options?: StateOptions,
|
|
189
189
|
): Promise<{ state: S; index: number }>
|
|
190
190
|
/**
|
|
@@ -980,10 +980,7 @@ export function createServer<
|
|
|
980
980
|
}
|
|
981
981
|
}
|
|
982
982
|
|
|
983
|
-
|
|
984
|
-
// not load-bearing: fall back to the raw store, which still reports a
|
|
985
|
-
// real storage failure.
|
|
986
|
-
const readCachedState = async (
|
|
983
|
+
const readStoreState = async (
|
|
987
984
|
store: A2Store,
|
|
988
985
|
sessionId: string,
|
|
989
986
|
reducerName: string,
|
|
@@ -1003,19 +1000,8 @@ export function createServer<
|
|
|
1003
1000
|
: { snapshotThroughIndex }),
|
|
1004
1001
|
},
|
|
1005
1002
|
)
|
|
1006
|
-
} catch {
|
|
1007
|
-
|
|
1008
|
-
return {
|
|
1009
|
-
headIndex: null,
|
|
1010
|
-
snapshot: null,
|
|
1011
|
-
events: await store.read(
|
|
1012
|
-
nsId(sessionId),
|
|
1013
|
-
throughIndex === undefined ? undefined : { throughIndex },
|
|
1014
|
-
),
|
|
1015
|
-
}
|
|
1016
|
-
} catch (err) {
|
|
1017
|
-
throw asStoreUnavailable(err)
|
|
1018
|
-
}
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
throw asStoreUnavailable(err)
|
|
1019
1005
|
}
|
|
1020
1006
|
}
|
|
1021
1007
|
|
|
@@ -1167,9 +1153,49 @@ export function createServer<
|
|
|
1167
1153
|
}
|
|
1168
1154
|
const persistence = Promise.resolve()
|
|
1169
1155
|
.then(() =>
|
|
1170
|
-
|
|
1156
|
+
telemetry.span(
|
|
1157
|
+
'a2.snapshot',
|
|
1158
|
+
{
|
|
1159
|
+
'a2.contract': name,
|
|
1160
|
+
'a2.session_id': plan.sessionId,
|
|
1161
|
+
'a2.snapshot.reducer': reducer.name,
|
|
1162
|
+
'a2.snapshot.index': snapshots.at(-1)!.index,
|
|
1163
|
+
'a2.snapshot.count': snapshots.length,
|
|
1164
|
+
},
|
|
1165
|
+
async (span) => {
|
|
1166
|
+
try {
|
|
1167
|
+
await store.putSnapshots(
|
|
1168
|
+
nsId(plan.sessionId),
|
|
1169
|
+
reducer.name,
|
|
1170
|
+
snapshots,
|
|
1171
|
+
)
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
try {
|
|
1174
|
+
span.recordError(error)
|
|
1175
|
+
} catch (telemetryError) {
|
|
1176
|
+
// oxlint-disable-next-line preserve-caught-error -- AggregateError preserves both failures.
|
|
1177
|
+
throw new AggregateError(
|
|
1178
|
+
[error, telemetryError],
|
|
1179
|
+
'Snapshot write and error reporting failed',
|
|
1180
|
+
{ cause: telemetryError },
|
|
1181
|
+
)
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
),
|
|
1171
1186
|
)
|
|
1172
|
-
.catch(() => {
|
|
1187
|
+
.catch((error: unknown) => {
|
|
1188
|
+
// oxlint-disable-next-line no-console -- telemetry failure must not hide failed background work.
|
|
1189
|
+
console.error(
|
|
1190
|
+
'[a2] error in a2.snapshot telemetry',
|
|
1191
|
+
{
|
|
1192
|
+
sessionId: plan.sessionId,
|
|
1193
|
+
reducer: reducer.name,
|
|
1194
|
+
index: snapshots.at(-1)!.index,
|
|
1195
|
+
},
|
|
1196
|
+
error,
|
|
1197
|
+
)
|
|
1198
|
+
})
|
|
1173
1199
|
track(persistence)
|
|
1174
1200
|
platformWaitUntil(persistence)
|
|
1175
1201
|
}
|
|
@@ -1187,7 +1213,7 @@ export function createServer<
|
|
|
1187
1213
|
batch: PendingStateRead[],
|
|
1188
1214
|
): Promise<void> => {
|
|
1189
1215
|
const plans = planStateReads(batch)
|
|
1190
|
-
let reads: StoreStateRead
|
|
1216
|
+
let reads: (StoreStateRead | Promise<StoreStateRead>)[]
|
|
1191
1217
|
try {
|
|
1192
1218
|
reads = store.readStates
|
|
1193
1219
|
? await store.readStates(
|
|
@@ -1202,45 +1228,33 @@ export function createServer<
|
|
|
1202
1228
|
})),
|
|
1203
1229
|
reducer.name,
|
|
1204
1230
|
)
|
|
1205
|
-
:
|
|
1206
|
-
|
|
1207
|
-
readCachedState(
|
|
1208
|
-
store,
|
|
1209
|
-
plan.sessionId,
|
|
1210
|
-
reducer.name,
|
|
1211
|
-
plan.throughIndex,
|
|
1212
|
-
plan.snapshotThroughIndex,
|
|
1213
|
-
),
|
|
1214
|
-
),
|
|
1215
|
-
)
|
|
1216
|
-
} catch {
|
|
1217
|
-
reads = null
|
|
1218
|
-
}
|
|
1219
|
-
if (!reads || reads.length !== plans.length) {
|
|
1220
|
-
try {
|
|
1221
|
-
reads = await Promise.all(
|
|
1222
|
-
plans.map((plan) =>
|
|
1223
|
-
readCachedState(
|
|
1231
|
+
: plans.map((plan) =>
|
|
1232
|
+
readStoreState(
|
|
1224
1233
|
store,
|
|
1225
1234
|
plan.sessionId,
|
|
1226
1235
|
reducer.name,
|
|
1227
1236
|
plan.throughIndex,
|
|
1228
1237
|
plan.snapshotThroughIndex,
|
|
1229
1238
|
),
|
|
1230
|
-
)
|
|
1239
|
+
)
|
|
1240
|
+
if (reads.length !== plans.length)
|
|
1241
|
+
throw new TypeError(
|
|
1242
|
+
'store.readStates must return one result per request',
|
|
1231
1243
|
)
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
for (const [position, plan] of plans.entries()) {
|
|
1238
|
-
try {
|
|
1239
|
-
await foldStatePlan(store, reducer, plan, reads[position]!)
|
|
1240
|
-
} catch (err) {
|
|
1241
|
-
for (const entry of plan.entries) entry.reject(err)
|
|
1242
|
-
}
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
const failure = asStoreUnavailable(error)
|
|
1246
|
+
for (const entry of batch) entry.reject(failure)
|
|
1247
|
+
return
|
|
1243
1248
|
}
|
|
1249
|
+
await Promise.all(
|
|
1250
|
+
plans.map(async (plan, position) => {
|
|
1251
|
+
try {
|
|
1252
|
+
await foldStatePlan(store, reducer, plan, await reads[position]!)
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
for (const entry of plan.entries) entry.reject(error)
|
|
1255
|
+
}
|
|
1256
|
+
}),
|
|
1257
|
+
)
|
|
1244
1258
|
}
|
|
1245
1259
|
|
|
1246
1260
|
const enqueueStateRead = (
|
|
@@ -1282,9 +1296,9 @@ export function createServer<
|
|
|
1282
1296
|
})
|
|
1283
1297
|
})
|
|
1284
1298
|
|
|
1285
|
-
const readState = async <S>(
|
|
1299
|
+
const readState = async <S, V>(
|
|
1286
1300
|
sessionId: string,
|
|
1287
|
-
reducer: Reducer<D, S>,
|
|
1301
|
+
reducer: Reducer<D, S, Record<never, never>, V>,
|
|
1288
1302
|
throughIndex?: number,
|
|
1289
1303
|
pinEventIndex?: number,
|
|
1290
1304
|
): Promise<{ state: S; index: number }> => {
|
|
@@ -1875,7 +1889,10 @@ export function createServer<
|
|
|
1875
1889
|
schedule: makeSchedule(id, trigger),
|
|
1876
1890
|
history: async (bounds?: { gte?: number; lte?: number }) =>
|
|
1877
1891
|
(await readHistory(id, bounds)).map(toPublic),
|
|
1878
|
-
state: <S>(
|
|
1892
|
+
state: <S, V>(
|
|
1893
|
+
reducer: Reducer<D, S, Record<never, never>, V>,
|
|
1894
|
+
stateOptions?: StateOptions,
|
|
1895
|
+
) => {
|
|
1879
1896
|
const through = stateOptions?.through ?? trigger?.index
|
|
1880
1897
|
if (through !== undefined && through !== 'latest') {
|
|
1881
1898
|
assertStateIndex(through)
|
package/src/store-redis-core.ts
CHANGED
|
@@ -611,20 +611,21 @@ return live
|
|
|
611
611
|
*/
|
|
612
612
|
const READ_STATE_LUA = `
|
|
613
613
|
local head = redis.call('GET', KEYS[1])
|
|
614
|
+
local head_index = head and cjson.decode(head)['index'] or nil
|
|
615
|
+
if head and (type(head_index) ~= 'number' or head_index < 0 or head_index > 9007199254740991 or head_index ~= math.floor(head_index)) then
|
|
616
|
+
return redis.error_reply('invalid snapshot head index')
|
|
617
|
+
end
|
|
614
618
|
local cap = ARGV[1]
|
|
615
619
|
local snapshot = nil
|
|
616
|
-
if
|
|
620
|
+
if head_index and (cap == '' or head_index <= tonumber(cap)) then
|
|
617
621
|
snapshot = head
|
|
618
|
-
|
|
619
|
-
local upper = cap == '' and '+inf' or cap
|
|
620
|
-
local historical = redis.call('ZREVRANGEBYSCORE', KEYS[2], upper, '-inf', 'LIMIT', 0, 1)
|
|
621
|
-
if #historical > 0 then
|
|
622
|
-
if not snapshot or tonumber(cjson.decode(historical[1])['index']) > tonumber(cjson.decode(snapshot)['index']) then
|
|
623
|
-
snapshot = historical[1]
|
|
624
|
-
end
|
|
622
|
+
else
|
|
623
|
+
local upper = cap == '' and '+inf' or cap
|
|
624
|
+
local historical = redis.call('ZREVRANGEBYSCORE', KEYS[2], upper, '-inf', 'LIMIT', 0, 1)
|
|
625
|
+
if #historical > 0 then snapshot = historical[1] end
|
|
625
626
|
end
|
|
626
627
|
local frontier = redis.call('GET', KEYS[3]) or '0'
|
|
627
|
-
return {
|
|
628
|
+
return { head_index or '', snapshot or '', frontier }
|
|
628
629
|
`
|
|
629
630
|
|
|
630
631
|
// ── wire helpers ─────────────────────────────────────────────────────
|
|
@@ -1351,13 +1352,10 @@ export function createRedisStoreCore(
|
|
|
1351
1352
|
stateOptions?.throughIndex ??
|
|
1352
1353
|
'',
|
|
1353
1354
|
],
|
|
1354
|
-
)) as [string, string, string]
|
|
1355
|
+
)) as [number | string, string, string]
|
|
1355
1356
|
const rawHead = reply[0]
|
|
1356
1357
|
const raw = reply[1]
|
|
1357
|
-
const
|
|
1358
|
-
rawHead === ''
|
|
1359
|
-
? null
|
|
1360
|
-
: (JSON.parse(rawHead) as { index: number; state: unknown })
|
|
1358
|
+
const headIndex = rawHead === '' ? null : Number(rawHead)
|
|
1361
1359
|
const parsed =
|
|
1362
1360
|
raw === ''
|
|
1363
1361
|
? null
|
|
@@ -1366,8 +1364,8 @@ export function createRedisStoreCore(
|
|
|
1366
1364
|
if (
|
|
1367
1365
|
!Number.isSafeInteger(currentFrontier) ||
|
|
1368
1366
|
currentFrontier < 0 ||
|
|
1369
|
-
(
|
|
1370
|
-
(!Number.isSafeInteger(
|
|
1367
|
+
(headIndex !== null &&
|
|
1368
|
+
(!Number.isSafeInteger(headIndex) || headIndex < 0)) ||
|
|
1371
1369
|
(parsed !== null &&
|
|
1372
1370
|
(!Number.isSafeInteger(parsed.index) || parsed.index < 0))
|
|
1373
1371
|
) {
|
|
@@ -1387,7 +1385,7 @@ export function createRedisStoreCore(
|
|
|
1387
1385
|
frontier,
|
|
1388
1386
|
)
|
|
1389
1387
|
return {
|
|
1390
|
-
headIndex
|
|
1388
|
+
headIndex,
|
|
1391
1389
|
snapshot: snapshot
|
|
1392
1390
|
? { index: snapshot.index, state: snapshot.state }
|
|
1393
1391
|
: null,
|
package/src/store.ts
CHANGED
|
@@ -270,6 +270,7 @@ export interface A2Store {
|
|
|
270
270
|
* operation. On a cache miss, `snapshot` is null and `events` is the full
|
|
271
271
|
* log. The snapshot is untrusted; core may reject it and issue a full
|
|
272
272
|
* `read()` when its state schema no longer accepts the cached value.
|
|
273
|
+
* Operational failures propagate without falling back to a full-log read.
|
|
273
274
|
*/
|
|
274
275
|
readState(
|
|
275
276
|
sessionId: string,
|
package/src/telemetry.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
export type A2AttributeValue = string | number | boolean
|
|
10
10
|
|
|
11
11
|
/** The spans a2 emits today. The catalogue grows with the surface. */
|
|
12
|
-
export type A2SpanName =
|
|
12
|
+
export type A2SpanName =
|
|
13
|
+
'a2.append' | 'a2.drain' | 'a2.event' | 'a2.state' | 'a2.snapshot'
|
|
13
14
|
|
|
14
15
|
export type A2SpanHandle = {
|
|
15
16
|
/** Attach or update an attribute mid-span (e.g. the outcome). */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ai-Cai-lCbj.d.ts","names":[],"sources":["../src/ai-control.ts","../src/ai-client-state.ts","../src/ai-projector.ts","../src/ai-progress-batches.ts","../src/ai.ts"],"mappings":"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"}
|