experimental-a2 0.7.0 → 0.8.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 (58) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/ai-server.d.ts +1 -1
  3. package/dist/ai-server.d.ts.map +1 -1
  4. package/dist/ai-server.js +13 -11
  5. package/dist/ai-server.js.map +1 -1
  6. package/dist/ai.d.ts +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/scheduler-qstash.d.ts +2 -2
  9. package/dist/scheduler-qstash.js +1 -1
  10. package/dist/scheduler-vercel.d.ts +2 -2
  11. package/dist/scheduler-vercel.js +1 -1
  12. package/dist/{server-286j79Mt.js → server-B2XNevQA.js} +123 -53
  13. package/dist/server-B2XNevQA.js.map +1 -0
  14. package/dist/{server-DgXmORIq.d.ts → server-DjPhHnbI.d.ts} +7 -4
  15. package/dist/server-DjPhHnbI.d.ts.map +1 -0
  16. package/dist/server.d.ts +3 -3
  17. package/dist/server.js +1 -1
  18. package/dist/store-N8PXxDAS.js.map +1 -1
  19. package/dist/{store-flRz1OWh.d.ts → store-RJO35BMj.d.ts} +25 -8
  20. package/dist/store-RJO35BMj.d.ts.map +1 -0
  21. package/dist/store-memory.d.ts +1 -1
  22. package/dist/store-memory.d.ts.map +1 -1
  23. package/dist/store-memory.js +79 -19
  24. package/dist/store-memory.js.map +1 -1
  25. package/dist/store-postgres.d.ts +1 -1
  26. package/dist/store-postgres.d.ts.map +1 -1
  27. package/dist/store-postgres.js +230 -101
  28. package/dist/store-postgres.js.map +1 -1
  29. package/dist/{store-redis-core-DEYO8Ryv.js → store-redis-core-DT01r4GZ.js} +167 -29
  30. package/dist/store-redis-core-DT01r4GZ.js.map +1 -0
  31. package/dist/store-redis-http.d.ts +1 -1
  32. package/dist/store-redis-http.js +2 -2
  33. package/dist/store-redis-http.js.map +1 -1
  34. package/dist/store-redis.d.ts +1 -1
  35. package/dist/store-redis.js +2 -2
  36. package/dist/store-redis.js.map +1 -1
  37. package/dist/store-sqlite.d.ts +1 -1
  38. package/dist/store-sqlite.d.ts.map +1 -1
  39. package/dist/store-sqlite.js +103 -19
  40. package/dist/store-sqlite.js.map +1 -1
  41. package/docs/concepts/02-handlers.mdx +4 -0
  42. package/docs/concepts/04-state.mdx +57 -9
  43. package/docs/guides/06-ai-agents.mdx +2 -1
  44. package/docs/reference/01-api.mdx +39 -16
  45. package/package.json +1 -1
  46. package/src/ai-server.ts +27 -10
  47. package/src/server.ts +242 -87
  48. package/src/store-memory.ts +138 -20
  49. package/src/store-postgres.ts +355 -138
  50. package/src/store-redis-core.ts +201 -27
  51. package/src/store-redis-http.ts +1 -1
  52. package/src/store-redis.ts +1 -1
  53. package/src/store-sqlite.ts +191 -34
  54. package/src/store.ts +27 -9
  55. package/dist/server-286j79Mt.js.map +0 -1
  56. package/dist/server-DgXmORIq.d.ts.map +0 -1
  57. package/dist/store-flRz1OWh.d.ts.map +0 -1
  58. package/dist/store-redis-core-DEYO8Ryv.js.map +0 -1
@@ -1123,21 +1123,45 @@ function createServer(options) {
1123
1123
  throw asStoreUnavailable(err);
1124
1124
  }
1125
1125
  };
1126
- const readCachedState = async (store, sessionId, reducerName) => {
1126
+ const readCachedState = async (store, sessionId, reducerName, throughIndex, snapshotThroughIndex) => {
1127
1127
  try {
1128
- return await store.readState(nsId(sessionId), reducerName);
1128
+ return await store.readState(nsId(sessionId), reducerName, throughIndex === void 0 && snapshotThroughIndex === void 0 ? void 0 : {
1129
+ ...throughIndex === void 0 ? {} : { throughIndex },
1130
+ ...snapshotThroughIndex === void 0 ? {} : { snapshotThroughIndex }
1131
+ });
1129
1132
  } catch {
1130
1133
  try {
1131
1134
  return {
1135
+ headIndex: null,
1132
1136
  snapshot: null,
1133
- events: await store.read(nsId(sessionId))
1137
+ events: await store.read(nsId(sessionId), throughIndex === void 0 ? void 0 : { throughIndex })
1134
1138
  };
1135
1139
  } catch (err) {
1136
1140
  throw asStoreUnavailable(err);
1137
1141
  }
1138
1142
  }
1139
1143
  };
1140
- const foldStateRead = async (store, sessionId, reducer, stateRead, span) => {
1144
+ const planStateReads = (batch) => {
1145
+ const bySession = /* @__PURE__ */ new Map();
1146
+ for (const entry of batch) {
1147
+ const entries = bySession.get(entry.sessionId);
1148
+ if (entries) entries.push(entry);
1149
+ else bySession.set(entry.sessionId, [entry]);
1150
+ }
1151
+ const plans = [];
1152
+ for (const [sessionId, entries] of bySession) {
1153
+ const boundaries = entries.flatMap((entry) => entry.throughIndex === void 0 ? [] : [entry.throughIndex]);
1154
+ const plan = {
1155
+ sessionId,
1156
+ entries
1157
+ };
1158
+ if (boundaries.length === entries.length) plan.throughIndex = Math.max(...boundaries);
1159
+ if (boundaries.length > 0) plan.snapshotThroughIndex = Math.min(...boundaries);
1160
+ plans.push(plan);
1161
+ }
1162
+ return plans;
1163
+ };
1164
+ const foldStatePlan = async (store, reducer, plan, stateRead) => {
1141
1165
  let state = cloneInitial(reducer.initialState);
1142
1166
  let index = 0;
1143
1167
  let snapshotOutcome = "miss";
@@ -1158,84 +1182,120 @@ function createServer(options) {
1158
1182
  snapshotOutcome = "hit";
1159
1183
  }
1160
1184
  }
1161
- span.setAttribute("a2.state.snapshot", snapshotOutcome);
1162
1185
  if (snapshotOutcome === "rejected") try {
1163
- rows = await store.read(nsId(sessionId));
1186
+ rows = await store.read(nsId(plan.sessionId), plan.throughIndex === void 0 ? void 0 : { throughIndex: plan.throughIndex });
1164
1187
  } catch (err) {
1165
1188
  throw asStoreUnavailable(err);
1166
1189
  }
1167
- for (const row of rows) {
1168
- state = reducer.fold(state, toPublic(row));
1169
- index = row.index;
1190
+ const ordered = plan.entries.toSorted((a, b) => (a.throughIndex ?? Number.POSITIVE_INFINITY) - (b.throughIndex ?? Number.POSITIVE_INFINITY));
1191
+ const writes = /* @__PURE__ */ new Map();
1192
+ let rowPosition = 0;
1193
+ for (const entry of ordered) {
1194
+ const target = entry.throughIndex ?? Number.POSITIVE_INFINITY;
1195
+ while (rowPosition < rows.length && rows[rowPosition].index <= target) {
1196
+ const row = rows[rowPosition];
1197
+ state = reducer.fold(state, toPublic(row));
1198
+ index = row.index;
1199
+ rowPosition += 1;
1200
+ }
1201
+ const resultState = cloneInitial(state);
1202
+ const folded = rowPosition;
1203
+ entry.span.setAttribute("a2.state.snapshot", snapshotOutcome);
1204
+ entry.span.setAttribute("a2.state.folded", folded);
1205
+ entry.span.setAttribute("a2.state.index", index);
1206
+ if (index > 0 && (folded > 0 || entry.pinEventIndex !== void 0)) {
1207
+ const advancesHead = stateRead.headIndex === null || index > stateRead.headIndex;
1208
+ const repairsHead = snapshotOutcome === "rejected" && index === stateRead.headIndex;
1209
+ if (!advancesHead && !repairsHead && entry.pinEventIndex === void 0) {
1210
+ entry.resolve({
1211
+ state: resultState,
1212
+ index
1213
+ });
1214
+ continue;
1215
+ }
1216
+ let write = writes.get(index);
1217
+ if (!write) {
1218
+ write = {
1219
+ state: cloneInitial(resultState),
1220
+ pinEventIndexes: /* @__PURE__ */ new Set()
1221
+ };
1222
+ writes.set(index, write);
1223
+ }
1224
+ if (entry.pinEventIndex !== void 0) write.pinEventIndexes.add(entry.pinEventIndex);
1225
+ }
1226
+ entry.resolve({
1227
+ state: resultState,
1228
+ index
1229
+ });
1230
+ }
1231
+ if (writes.size > 0) {
1232
+ const snapshots = [];
1233
+ for (const [snapshotIndex, write] of writes) {
1234
+ const snapshot = {
1235
+ index: snapshotIndex,
1236
+ state: write.state
1237
+ };
1238
+ if (write.pinEventIndexes.size > 0) snapshot.pinEventIndexes = [...write.pinEventIndexes];
1239
+ snapshots.push(snapshot);
1240
+ }
1241
+ const persistence = Promise.resolve().then(() => store.putSnapshots(nsId(plan.sessionId), reducer.name, snapshots)).catch(() => {});
1242
+ track(persistence);
1243
+ platformWaitUntil(persistence);
1170
1244
  }
1171
- span.setAttribute("a2.state.folded", rows.length);
1172
- span.setAttribute("a2.state.index", index);
1173
- return {
1174
- state,
1175
- index,
1176
- folded: rows.length
1177
- };
1178
1245
  };
1179
1246
  const pendingStateReads = /* @__PURE__ */ new Map();
1180
- const flushStateReads = async (store, reducerName, batch) => {
1247
+ const flushStateReads = async (store, reducer, batch) => {
1248
+ const plans = planStateReads(batch);
1181
1249
  let reads;
1182
1250
  try {
1183
- reads = store.readStates ? await store.readStates(batch.map((entry) => nsId(entry.sessionId)), reducerName) : null;
1251
+ reads = store.readStates ? await store.readStates(plans.map((plan) => ({
1252
+ sessionId: nsId(plan.sessionId),
1253
+ ...plan.throughIndex === void 0 ? {} : { throughIndex: plan.throughIndex },
1254
+ ...plan.snapshotThroughIndex === void 0 ? {} : { snapshotThroughIndex: plan.snapshotThroughIndex }
1255
+ })), reducer.name) : await Promise.all(plans.map((plan) => readCachedState(store, plan.sessionId, reducer.name, plan.throughIndex, plan.snapshotThroughIndex)));
1184
1256
  } catch {
1185
1257
  reads = null;
1186
1258
  }
1187
- if (!reads || reads.length !== batch.length) {
1188
- for (const entry of batch) entry.resolve(readCachedState(store, entry.sessionId, reducerName));
1259
+ if (!reads || reads.length !== plans.length) try {
1260
+ reads = await Promise.all(plans.map((plan) => readCachedState(store, plan.sessionId, reducer.name, plan.throughIndex, plan.snapshotThroughIndex)));
1261
+ } catch (err) {
1262
+ for (const entry of batch) entry.reject(err);
1189
1263
  return;
1190
1264
  }
1191
- const seen = /* @__PURE__ */ new Set();
1192
- for (const [position, entry] of batch.entries()) {
1193
- const shared = seen.has(entry.sessionId);
1194
- seen.add(entry.sessionId);
1195
- try {
1196
- const read = reads[position];
1197
- entry.resolve(shared ? structuredClone(read) : read);
1198
- } catch {
1199
- entry.resolve(readCachedState(store, entry.sessionId, reducerName));
1200
- }
1265
+ for (const [position, plan] of plans.entries()) try {
1266
+ await foldStatePlan(store, reducer, plan, reads[position]);
1267
+ } catch (err) {
1268
+ for (const entry of plan.entries) entry.reject(err);
1201
1269
  }
1202
1270
  };
1203
- const enqueueStateRead = (store, sessionId, reducerName) => new Promise((resolve) => {
1271
+ const enqueueStateRead = (store, sessionId, reducer, throughIndex, pinEventIndex, span) => new Promise((resolve, reject) => {
1272
+ const reducerName = reducer.name;
1204
1273
  let batch = pendingStateReads.get(reducerName);
1205
1274
  if (!batch) {
1206
1275
  const opened = [];
1207
1276
  pendingStateReads.set(reducerName, opened);
1208
1277
  queueMicrotask(() => {
1209
1278
  pendingStateReads.delete(reducerName);
1210
- flushStateReads(store, reducerName, opened);
1279
+ flushStateReads(store, reducer, opened);
1211
1280
  });
1212
1281
  batch = opened;
1213
1282
  }
1214
1283
  batch.push({
1215
1284
  sessionId,
1216
- resolve
1285
+ ...throughIndex === void 0 ? {} : { throughIndex },
1286
+ ...pinEventIndex === void 0 ? {} : { pinEventIndex },
1287
+ span,
1288
+ resolve,
1289
+ reject
1217
1290
  });
1218
1291
  });
1219
- const readState = async (sessionId, reducer) => {
1292
+ const readState = async (sessionId, reducer, throughIndex, pinEventIndex) => {
1220
1293
  const store = await resolveStore();
1221
1294
  return telemetry.span("a2.state", {
1222
1295
  "a2.contract": name,
1223
1296
  "a2.session_id": sessionId,
1224
1297
  "a2.state.reducer": reducer.name
1225
- }, async (span) => {
1226
- const stateRead = store.readStates ? await enqueueStateRead(store, sessionId, reducer.name) : await readCachedState(store, sessionId, reducer.name);
1227
- const { state, index, folded } = await foldStateRead(store, sessionId, reducer, stateRead, span);
1228
- if (folded > 0) {
1229
- const snapshotState = cloneInitial(state);
1230
- const write = Promise.resolve().then(() => store.putSnapshot(nsId(sessionId), reducer.name, index, snapshotState)).catch(() => {});
1231
- track(write);
1232
- platformWaitUntil(write);
1233
- }
1234
- return {
1235
- state,
1236
- index
1237
- };
1238
- });
1298
+ }, (span) => enqueueStateRead(store, sessionId, reducer, throughIndex, pinEventIndex, span));
1239
1299
  };
1240
1300
  const makeSchedule = (sessionId, trigger) => {
1241
1301
  const triggerEventId = trigger?.id ?? null;
@@ -1246,11 +1306,14 @@ function createServer(options) {
1246
1306
  if (events.length === 0) throw new TypeError("schedule requires at least one event");
1247
1307
  const dueAt = scheduleDueAt(timing, triggerCreatedAt ?? Date.now());
1248
1308
  const payloads = events.map((event) => cloneScheduledPayload(event.payload));
1249
- const snapshottedEvents = events.map((event, index) => ({
1309
+ const snapshottedEvents = events.map((event, index) => event.id === void 0 ? {
1310
+ type: event.type,
1311
+ payload: payloads[index]
1312
+ } : {
1250
1313
  type: event.type,
1251
1314
  payload: payloads[index],
1252
- ...event.id === void 0 ? {} : { id: event.id }
1253
- }));
1315
+ id: event.id
1316
+ });
1254
1317
  const validated = validateEvents(sessionId, structuredClone(snapshottedEvents), false);
1255
1318
  const scheduleId = await deterministicScheduleId(name, sessionId, triggerEventId, scheduleName);
1256
1319
  const scheduledEvents = await Promise.all(validated.map(async (event, item) => ({
@@ -1512,7 +1575,11 @@ function createServer(options) {
1512
1575
  append,
1513
1576
  schedule: makeSchedule(id, trigger),
1514
1577
  history: async (bounds) => (await readHistory(id, bounds)).map(toPublic),
1515
- state: (reducer) => readState(id, reducer),
1578
+ state: (reducer, stateOptions) => {
1579
+ const through = stateOptions?.through ?? trigger?.index;
1580
+ if (through !== void 0 && through !== "latest") assertStateIndex(through);
1581
+ return readState(id, reducer, through === "latest" ? void 0 : through, trigger && through !== "latest" ? trigger.index : void 0);
1582
+ },
1516
1583
  stream: (opts) => {
1517
1584
  const startAfter = opts?.startAfter ?? 0;
1518
1585
  assertStreamIndex(startAfter);
@@ -1998,6 +2065,9 @@ function assertHistoryIndex(name, value) {
1998
2065
  function assertStreamIndex(value) {
1999
2066
  if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("stream.startAfter must be a non-negative safe integer");
2000
2067
  }
2068
+ function assertStateIndex(value) {
2069
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("state.through must be a non-negative safe integer");
2070
+ }
2001
2071
  function assertAppendName(name) {
2002
2072
  if (typeof name !== "string" || name.length === 0) throw new TypeError("handler append name must be a non-empty string");
2003
2073
  }
@@ -2089,4 +2159,4 @@ function cloneInitial(initial) {
2089
2159
  //#endregion
2090
2160
  export { platformVercelOidcToken as i, deliverSchedulerAppend as n, setServerFetchHooks as r, createServer as t };
2091
2161
 
2092
- //# sourceMappingURL=server-286j79Mt.js.map
2162
+ //# sourceMappingURL=server-B2XNevQA.js.map