experimental-a2 0.2.0 → 0.4.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 (92) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/dist/ai-B4YhEnfw.d.ts +333 -0
  3. package/dist/ai-server.d.ts +48 -9
  4. package/dist/ai-server.js +121 -49
  5. package/dist/ai.d.ts +2 -303
  6. package/dist/ai.js +231 -86
  7. package/dist/cli-B3VuxoDe.js +597 -0
  8. package/dist/cli-bin.d.ts +1 -0
  9. package/dist/cli-bin.js +5 -0
  10. package/dist/cli.d.ts +19 -0
  11. package/dist/cli.js +2 -0
  12. package/dist/client-BrfDXQ8A.d.ts +155 -0
  13. package/dist/client-Bt4tAKi9.js +798 -0
  14. package/dist/client.d.ts +2 -90
  15. package/dist/client.js +1 -409
  16. package/dist/{contract-CG_adnu_.js → contract-48bUMgcL.js} +10 -2
  17. package/dist/{contract-C_3dIIEU.d.ts → contract-jIfaR085.d.ts} +62 -8
  18. package/dist/devtools-J_jZ2vQf.d.ts +151 -0
  19. package/dist/devtools-kJJaORn-.js +338 -0
  20. package/dist/devtools-server.browser.js +1 -1
  21. package/dist/devtools-server.d.ts +2 -2
  22. package/dist/devtools-server.js +224 -43
  23. package/dist/devtools.d.ts +2 -0
  24. package/dist/devtools.js +2 -0
  25. package/dist/{errors-BJRMd-h6.js → errors-BQuJpe82.js} +4 -4
  26. package/dist/{errors-xL_JTXsY.d.ts → errors-W6nwJ-fm.d.ts} +1 -1
  27. package/dist/http.d.ts +71 -13
  28. package/dist/http.js +302 -41
  29. package/dist/{idempotent-replay-BMyHrP0L.js → idempotent-replay-DuqEkYA7.js} +2 -2
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +2 -2
  32. package/dist/{inspection-E7qbD0Xj.js → inspection-DaxB5jM2.js} +2 -1
  33. package/dist/internal-aEotMzu_.js +209 -0
  34. package/dist/otel.d.ts +1 -1
  35. package/dist/platform-B4TnJtWu.js +32 -0
  36. package/dist/react.d.ts +37 -14
  37. package/dist/react.js +26 -15
  38. package/dist/scheduler-qstash.d.ts +78 -0
  39. package/dist/scheduler-qstash.js +499 -0
  40. package/dist/scheduler-task-BpzhPnRS.js +54 -0
  41. package/dist/{recovery-vercel.d.ts → scheduler-vercel.d.ts} +17 -24
  42. package/dist/scheduler-vercel.js +226 -0
  43. package/dist/server-CcNnFnoW.js +1405 -0
  44. package/dist/server-YtPq7hjw.d.ts +260 -0
  45. package/dist/server.d.ts +4 -155
  46. package/dist/server.js +2 -2
  47. package/dist/{log-ldf5g8Cx.d.ts → store-C3sNAaBT.d.ts} +111 -35
  48. package/dist/{log-yJbXUf72.js → store-N8PXxDAS.js} +1 -1
  49. package/dist/store-codec-DTG0Ftek.js +8 -0
  50. package/dist/store-memory.d.ts +11 -0
  51. package/dist/{log-memory.js → store-memory.js} +127 -24
  52. package/dist/{log-polling-6COoN60V.js → store-polling-DgrrAE3d.js} +7 -6
  53. package/dist/{log-postgres.d.ts → store-postgres.d.ts} +6 -6
  54. package/dist/{log-postgres.js → store-postgres.js} +158 -24
  55. package/dist/{log-redis.js → store-redis-core-DWqx3F47.js} +294 -156
  56. package/dist/store-redis-http.d.ts +21 -0
  57. package/dist/store-redis-http.js +70 -0
  58. package/dist/store-redis.d.ts +37 -0
  59. package/dist/store-redis.js +298 -0
  60. package/dist/{log-sqlite.d.ts → store-sqlite.d.ts} +6 -6
  61. package/dist/{log-sqlite.js → store-sqlite.js} +116 -22
  62. package/dist/{telemetry-Cso0qyHQ.d.ts → telemetry-BjYHTfh2.d.ts} +1 -1
  63. package/dist/testing.browser.d.ts +1 -0
  64. package/dist/testing.browser.js +4 -0
  65. package/dist/testing.d.ts +31 -0
  66. package/dist/testing.js +101 -0
  67. package/dist/wire-DCUZBUlT.js +222 -0
  68. package/docs/01-quickstart.mdx +4 -5
  69. package/docs/concepts/01-contracts.mdx +21 -17
  70. package/docs/concepts/02-handlers.mdx +7 -7
  71. package/docs/concepts/03-durability.mdx +26 -29
  72. package/docs/concepts/04-state.mdx +18 -21
  73. package/docs/guides/01-timers.mdx +154 -54
  74. package/docs/guides/02-cancellation.mdx +30 -4
  75. package/docs/guides/03-react.mdx +20 -21
  76. package/docs/guides/04-local-first.mdx +1 -1
  77. package/docs/guides/05-production.mdx +321 -60
  78. package/docs/guides/06-ai-agents.mdx +249 -49
  79. package/docs/guides/07-devtools.mdx +137 -12
  80. package/docs/guides/08-application-data.mdx +1 -1
  81. package/docs/guides/09-presence.mdx +284 -0
  82. package/docs/guides/10-transports.mdx +131 -0
  83. package/docs/index.mdx +22 -46
  84. package/docs/reference/01-api.mdx +751 -102
  85. package/docs/reference/02-errors.mdx +10 -5
  86. package/package.json +23 -6
  87. package/dist/internal-D6wNxTck.js +0 -36
  88. package/dist/log-memory.d.ts +0 -11
  89. package/dist/log-redis.d.ts +0 -31
  90. package/dist/recovery-vercel.js +0 -119
  91. package/dist/server-DJgD2YWP.js +0 -877
  92. package/dist/wire-BVsgR8o9.js +0 -62
package/dist/ai.js CHANGED
@@ -1,5 +1,6 @@
1
- import { t as assertSyncSchema } from "./validate-XKT4FSNn.js";
2
- import { t as contract } from "./contract-CG_adnu_.js";
1
+ import { n as validateSync, t as assertSyncSchema } from "./validate-XKT4FSNn.js";
2
+ import { t as contract } from "./contract-48bUMgcL.js";
3
+ import { l as ambientToolScopeStorage } from "./internal-aEotMzu_.js";
3
4
  //#region src/ai-projector.ts
4
5
  const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
6
  const cloneMessage = (messageId, base) => structuredClone(base ?? {
@@ -423,15 +424,18 @@ const messageValue = (value) => isRecord(value) && typeof value["id"] === "strin
423
424
  const validateMessage = (value, messageSchema) => {
424
425
  if (!messageValue(value)) return issue("invalid AI SDK UIMessage");
425
426
  if (!messageSchema) return { value };
426
- const result = messageSchema["~standard"].validate(value);
427
- if (result instanceof Promise) throw new TypeError("the AI message schema must validate synchronously");
428
- return result;
427
+ return validateSync(messageSchema, value, "the AI message");
429
428
  };
430
429
  const messagePayloadSchema = (messageSchema) => schema("ai.message.created", (value) => {
431
430
  if (!isRecord(value)) return issue("invalid ai.message.created payload");
432
431
  const message = validateMessage(value["message"], messageSchema);
433
432
  if (message.issues) return { issues: message.issues };
434
- return { value: { message: message.value } };
433
+ const generate = value["generate"];
434
+ if (generate !== void 0 && typeof generate !== "boolean") return issue("invalid ai.message.created generate flag");
435
+ return { value: {
436
+ message: message.value,
437
+ ...generate === false ? { generate: false } : {}
438
+ } };
435
439
  });
436
440
  const progressPayloadSchema = schema("ai.generation.progress", (value) => {
437
441
  if (!isRecord(value) || !isJSONCompatible(value) || !stringField(value, "requestId") || !stringField(value, "messageId") || !stringField(value, "generationId") || !stringField(value, "responseMessageId") || typeof value["sequence"] !== "number" || !Number.isInteger(value["sequence"]) || value["sequence"] < 0 || !Array.isArray(value["chunks"]) || !value["chunks"].every((chunk) => isRecord(chunk) && typeof chunk["type"] === "string")) return issue("invalid ai.generation.progress payload");
@@ -464,7 +468,7 @@ function createEvents(options) {
464
468
  "ai.session.closed": jsonObject("ai.session.closed", (value) => optionalStringField(value, "reason")),
465
469
  "ai.message.created": messagePayloadSchema(messageSchema),
466
470
  "ai.message.completed": jsonObject("ai.message.completed", (value) => stringField(value, "messageId")),
467
- "ai.message.interrupted": jsonObject("ai.message.interrupted", (value) => stringField(value, "messageId") && optionalStringField(value, "generationId") && optionalStringField(value, "reason") && (value["lastSeenIndex"] === void 0 || typeof value["lastSeenIndex"] === "number" && Number.isInteger(value["lastSeenIndex"]) && value["lastSeenIndex"] >= 0)),
471
+ "ai.message.interrupted": jsonObject("ai.message.interrupted", (value) => stringField(value, "messageId") && (stringField(value, "generationId") && value["requestId"] === void 0 || stringField(value, "requestId") && value["generationId"] === void 0) && optionalStringField(value, "reason") && (value["lastSeenIndex"] === void 0 || typeof value["lastSeenIndex"] === "number" && Number.isInteger(value["lastSeenIndex"]) && value["lastSeenIndex"] >= 0)),
468
472
  "ai.generation.requested": jsonObject("ai.generation.requested", (value) => stringField(value, "messageId") && optionalStringField(value, "responseMessageId") && (value["reason"] === "message" || value["reason"] === "tool" || value["reason"] === "input" || value["reason"] === "retry")),
469
473
  "ai.retry.requested": jsonObject("ai.retry.requested", (value) => stringField(value, "messageId") && stringField(value, "responseMessageId") && stringField(value, "retryId")),
470
474
  "ai.generation.started": jsonObject("ai.generation.started", (value) => stringField(value, "requestId") && stringField(value, "messageId") && stringField(value, "generationId") && stringField(value, "responseMessageId") && typeof value["attempt"] === "number" && Number.isInteger(value["attempt"]) && value["attempt"] > 0 && stringField(value, "model")),
@@ -583,7 +587,7 @@ const toolResultChunk = (payload) => {
583
587
  };
584
588
  const waitingStatus = (state) => {
585
589
  if (state.pendingApprovals.length > 0 || state.pendingInputs.length > 0) return "waiting";
586
- return state.activeGeneration !== null || state.tools.some((tool) => tool.status === "running") ? "generating" : "idle";
590
+ return state.activeGeneration !== null || state.tools.some((activity) => activity.status === "running") ? "generating" : "idle";
587
591
  };
588
592
  const lifecycleStatus = (previous, next) => previous.status === "failed" ? "failed" : waitingStatus(next);
589
593
  const isStaleGeneration = (state, messageId, generationId) => {
@@ -610,12 +614,12 @@ const reduceToolActivity = (tools, event) => {
610
614
  if (event.type === "ai.tool.called") {
611
615
  const payload = event.payload;
612
616
  const { providerMetadata, ...called } = payload;
613
- const tool = {
617
+ const activity = {
614
618
  ...called,
615
619
  ...providerMetadata === void 0 ? {} : { callProviderMetadata: providerMetadata },
616
620
  status: "running"
617
621
  };
618
- return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), tool];
622
+ return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
619
623
  }
620
624
  const payload = event.payload;
621
625
  const existing = tools.find((candidate) => candidate.generationId === payload.generationId && candidate.toolCallId === payload.toolCallId);
@@ -625,7 +629,7 @@ const reduceToolActivity = (tools, event) => {
625
629
  const providerExecuted = payload.providerExecuted ?? existing?.providerExecuted;
626
630
  const toolMetadata = payload.toolMetadata ?? existing?.toolMetadata;
627
631
  const resultProviderMetadata = payload.providerMetadata ?? existing?.resultProviderMetadata;
628
- const tool = {
632
+ const activity = {
629
633
  requestId: payload.requestId,
630
634
  messageId: payload.messageId,
631
635
  generationId: payload.generationId,
@@ -645,19 +649,60 @@ const reduceToolActivity = (tools, event) => {
645
649
  ...existing?.title === void 0 ? {} : { title: existing.title },
646
650
  status: payload.denied ? "denied" : payload.error ? "failed" : payload.preliminary ? "running" : "completed"
647
651
  };
648
- return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), tool];
652
+ return [...tools.filter((candidate) => candidate.generationId !== payload.generationId || candidate.toolCallId !== payload.toolCallId), activity];
649
653
  };
650
654
  const projectedTools = (projection) => projection.toolEvents.reduce((tools, event) => reduceToolActivity(tools, event), projection.baseTools);
651
- const stopGenerationTools = (tools, generationId, responseMessageId, error) => tools.map((tool) => tool.generationId === generationId || tool.messageId === responseMessageId ? {
652
- ...tool,
655
+ const restoreGenerationBase = (state, generationId, responseMessageId, reason) => {
656
+ const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
657
+ return {
658
+ ...state,
659
+ messages: projection?.baseMessage === void 0 ? state.messages.filter((message) => message.id !== responseMessageId) : upsertMessage(state.messages, projection.baseMessage),
660
+ tools: projection === null ? state.tools.filter((activity) => activity.generationId !== generationId) : projection.baseTools,
661
+ pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== generationId),
662
+ pendingInputs: state.pendingInputs.filter((input) => input.generationId !== generationId),
663
+ activeGeneration: null,
664
+ activeProjection: null,
665
+ terminalGenerations: {
666
+ ...state.terminalGenerations,
667
+ [generationId]: reason
668
+ },
669
+ compaction: projection === null ? state.compaction?.generationId === generationId ? null : state.compaction : projection.baseCompaction ?? null
670
+ };
671
+ };
672
+ const stopGenerationTools = (tools, generationId, responseMessageId, error) => tools.map((activity) => activity.status === "running" && (generationId === void 0 ? activity.messageId === responseMessageId : activity.generationId === generationId) ? {
673
+ ...activity,
653
674
  status: "failed",
654
675
  error,
655
676
  preliminary: false
656
- } : tool);
677
+ } : activity);
678
+ const interruptGenerationProjection = (state, generationId, responseMessageId, cutoff) => {
679
+ const projection = state.activeProjection?.generationId === generationId ? state.activeProjection : null;
680
+ if (projection === null) return null;
681
+ const retained = {
682
+ ...projection,
683
+ batches: projection.batches.filter((batch) => batch.index <= cutoff),
684
+ toolEvents: projection.toolEvents.filter((toolEvent) => toolEvent.index <= cutoff),
685
+ approvalEvents: projection.approvalEvents.filter((approvalEvent) => approvalEvent.index <= cutoff)
686
+ };
687
+ const shouldProject = retained.baseMessage !== void 0 || retained.batches.length > 0;
688
+ const restored = restoreGenerationBase(state, generationId, responseMessageId, "interrupted");
689
+ return {
690
+ ...restored,
691
+ messages: shouldProject ? upsertMessage(restored.messages, interruptUIMessage(projectedMessage(retained))) : restored.messages,
692
+ tools: stopGenerationTools(projectedTools(retained), generationId, responseMessageId, "Tool execution was interrupted."),
693
+ usage: projection.completionIndex === void 0 || projection.completionIndex <= cutoff ? restored.usage : restored.usage.filter((entry) => entry.generationId !== generationId)
694
+ };
695
+ };
657
696
  const generationRequestId = (generationId) => {
658
697
  const markerIndex = generationId.lastIndexOf(":generation:");
659
698
  return markerIndex === -1 ? void 0 : generationId.slice(0, markerIndex);
660
699
  };
700
+ const terminalGenerationReason = (state, generationId) => {
701
+ const terminal = state.terminalGenerations?.[generationId];
702
+ if (terminal !== void 0) return terminal;
703
+ const requestId = generationRequestId(generationId);
704
+ return requestId !== void 0 && state.terminalRequestIds?.[requestId] === true ? "interrupted" : void 0;
705
+ };
661
706
  /** Pure projection of the built-in AI protocol. Unknown extension events are ignored. */
662
707
  function reduceAIState(state, event) {
663
708
  if (state.status === "closed") return state;
@@ -687,6 +732,26 @@ function reduceAIState(state, event) {
687
732
  messages: upsertMessage(state.messages, message)
688
733
  };
689
734
  }
735
+ case "ai.message.completed": {
736
+ const { messageId } = event.payload;
737
+ const generationId = state.responseGenerationIds[messageId];
738
+ if (generationId === void 0 || terminalGenerationReason(state, generationId) !== void 0) return state;
739
+ const requestId = generationRequestId(generationId);
740
+ return {
741
+ ...state,
742
+ activeRequestId: null,
743
+ activeResponseMessageId: null,
744
+ activeProjection: state.activeProjection?.generationId === generationId ? null : state.activeProjection,
745
+ terminalGenerations: {
746
+ ...state.terminalGenerations,
747
+ [generationId]: "completed"
748
+ },
749
+ ...requestId === void 0 ? {} : { terminalRequestIds: {
750
+ ...state.terminalRequestIds,
751
+ [requestId]: true
752
+ } }
753
+ };
754
+ }
690
755
  case "ai.generation.requested": {
691
756
  const payload = event.payload;
692
757
  const schedulesToolContinuation = payload.reason === "tool";
@@ -694,10 +759,12 @@ function reduceAIState(state, event) {
694
759
  const ownerGenerationId = state.responseGenerationIds[responseMessageId] ?? state.activeGeneration?.generationId;
695
760
  const ownerRequestId = ownerGenerationId === void 0 ? void 0 : generationRequestId(ownerGenerationId);
696
761
  const requestId = event.id;
762
+ if (requestId !== void 0 && state.terminalRequestIds?.[requestId] === true) return state;
697
763
  if (!(payload.reason === "message" || (payload.reason === "tool" ? ownerGenerationId !== void 0 && requestId === `ai.generate:tools:${ownerGenerationId}` && state.activeRequestId === ownerRequestId && state.status !== "failed" : payload.reason === "retry" ? state.status === "failed" : ownerGenerationId !== void 0 && requestId?.startsWith(`ai.generate:input:${encodeURIComponent(responseMessageId)}:${encodeURIComponent(ownerGenerationId)}:`) === true && state.activeRequestId === ownerRequestId && state.status !== "failed"))) return state;
698
764
  return {
699
765
  ...state,
700
766
  messages: payload.reason === "retry" && payload.responseMessageId !== void 0 ? state.messages.filter((message) => message.id !== payload.responseMessageId) : state.messages,
767
+ activeProjection: payload.reason === "retry" ? null : state.activeProjection,
701
768
  status: schedulesToolContinuation ? state.status : "generating",
702
769
  activeRequestId: requestId ?? null,
703
770
  activeResponseMessageId: responseMessageId,
@@ -706,27 +773,33 @@ function reduceAIState(state, event) {
706
773
  }
707
774
  case "ai.generation.started": {
708
775
  const payload = event.payload;
776
+ if (state.terminalRequestIds?.[payload.requestId] === true || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
709
777
  if (state.activeRequestId !== null && state.activeRequestId !== payload.requestId) return state;
710
778
  const currentOwner = state.responseGenerationIds[payload.responseMessageId];
779
+ const currentTerminal = currentOwner === void 0 ? void 0 : terminalGenerationReason(state, currentOwner);
780
+ if (currentTerminal === "completed" || currentTerminal === "interrupted") return state;
711
781
  const marker = ":generation:";
712
782
  const currentAttempt = currentOwner?.startsWith(`${payload.requestId}${marker}`) ? Number(currentOwner.slice(`${payload.requestId}${marker}`.length)) : void 0;
713
783
  if (currentAttempt !== void 0 && Number.isFinite(currentAttempt) && currentAttempt >= payload.attempt) return state;
714
- const baseMessage = state.messages.find((message) => message.id === payload.responseMessageId);
784
+ const activeGeneration = state.activeGeneration;
785
+ const baseState = activeGeneration !== null && activeGeneration.requestId === payload.requestId && activeGeneration.responseMessageId === payload.responseMessageId && activeGeneration.attempt < payload.attempt && state.activeProjection?.generationId === activeGeneration.generationId ? restoreGenerationBase(state, activeGeneration.generationId, activeGeneration.responseMessageId, "superseded") : state;
786
+ const baseMessage = baseState.messages.find((message) => message.id === payload.responseMessageId);
715
787
  return {
716
- ...state,
788
+ ...baseState,
717
789
  status: "generating",
718
790
  activeGeneration: payload,
719
791
  activeResponseMessageId: null,
720
792
  responseGenerationIds: {
721
- ...state.responseGenerationIds,
793
+ ...baseState.responseGenerationIds,
722
794
  [payload.responseMessageId]: payload.generationId
723
795
  },
724
796
  activeProjection: {
725
797
  generationId: payload.generationId,
726
798
  responseMessageId: payload.responseMessageId,
727
799
  ...baseMessage === void 0 ? {} : { baseMessage },
800
+ baseCompaction: baseState.compaction,
728
801
  batches: [],
729
- baseTools: state.tools,
802
+ baseTools: baseState.tools,
730
803
  toolEvents: [],
731
804
  approvalEvents: []
732
805
  },
@@ -735,6 +808,7 @@ function reduceAIState(state, event) {
735
808
  }
736
809
  case "ai.generation.progress": {
737
810
  const payload = event.payload;
811
+ if (terminalGenerationReason(state, payload.generationId) !== void 0) return state;
738
812
  const activeProjection = state.activeProjection;
739
813
  if (state.activeGeneration?.generationId !== payload.generationId || activeProjection?.generationId !== payload.generationId) return state;
740
814
  const projection = {
@@ -753,6 +827,7 @@ function reduceAIState(state, event) {
753
827
  }
754
828
  case "ai.generation.completed": {
755
829
  const payload = event.payload;
830
+ if (terminalGenerationReason(state, payload.generationId) !== void 0) return state;
756
831
  if (state.activeGeneration?.generationId !== payload.generationId) return state;
757
832
  const usage = payload.usage ? [...state.usage.filter((entry) => entry.generationId !== payload.generationId), {
758
833
  generationId: payload.generationId,
@@ -761,7 +836,10 @@ function reduceAIState(state, event) {
761
836
  const next = {
762
837
  ...state,
763
838
  activeGeneration: null,
764
- activeProjection: null,
839
+ activeProjection: state.activeProjection?.generationId === payload.generationId ? {
840
+ ...state.activeProjection,
841
+ completionIndex: event.index
842
+ } : state.activeProjection,
765
843
  usage
766
844
  };
767
845
  return {
@@ -771,77 +849,111 @@ function reduceAIState(state, event) {
771
849
  }
772
850
  case "ai.generation.failed": {
773
851
  const payload = event.payload;
774
- if (state.activeGeneration?.generationId !== payload.generationId && !(payload.stepLimit === true && state.responseGenerationIds[payload.responseMessageId] === payload.generationId)) return state;
775
- if (payload.superseded === true) return {
776
- ...state,
777
- messages: state.messages.filter((message) => message.id !== payload.responseMessageId),
778
- tools: state.tools.filter((tool) => tool.generationId !== payload.generationId),
779
- pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== payload.generationId),
780
- pendingInputs: state.pendingInputs.filter((input) => input.generationId !== payload.generationId),
781
- activeGeneration: null,
782
- activeProjection: null,
783
- compaction: state.compaction?.generationId === payload.generationId ? null : state.compaction
784
- };
785
- const projection = state.activeProjection;
786
- const shouldProject = projection?.generationId === payload.generationId && (projection.baseMessage !== void 0 || projection.batches.length > 0);
852
+ if (terminalGenerationReason(state, payload.generationId) !== void 0) return state;
853
+ if (state.activeGeneration?.generationId !== payload.generationId && !(payload.stepLimit === true && state.responseGenerationIds[payload.responseMessageId] === payload.generationId && state.activeRequestId === `ai.generate:tools:${payload.generationId}` && state.activeResponseMessageId === payload.responseMessageId)) return state;
854
+ if (payload.superseded === true) return restoreGenerationBase(state, payload.generationId, payload.responseMessageId, "superseded");
855
+ const projection = state.activeProjection?.generationId === payload.generationId ? state.activeProjection : null;
856
+ const shouldProject = projection !== null && (projection.baseMessage !== void 0 || projection.batches.length > 0);
787
857
  return {
788
858
  ...state,
789
859
  messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage(projection), `Generation failed before tool completion: ${payload.error}`)) : state.messages,
790
860
  status: "failed",
791
861
  activeGeneration: null,
792
862
  activeResponseMessageId: null,
793
- activeProjection: null,
794
- compaction: state.compaction?.generationId === payload.generationId ? null : state.compaction,
863
+ activeProjection: projection,
864
+ terminalGenerations: {
865
+ ...state.terminalGenerations,
866
+ [payload.generationId]: "failed"
867
+ },
868
+ terminalRequestIds: {
869
+ ...state.terminalRequestIds,
870
+ [payload.requestId]: true,
871
+ ...state.activeRequestId === null ? {} : { [state.activeRequestId]: true }
872
+ },
873
+ compaction: projection?.generationId === payload.generationId ? projection.baseCompaction ?? null : state.compaction?.generationId === payload.generationId ? null : state.compaction,
795
874
  pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== payload.generationId),
875
+ pendingInputs: state.pendingInputs.filter((input) => input.generationId !== payload.generationId),
796
876
  tools: stopGenerationTools(state.tools, payload.generationId, payload.responseMessageId, payload.error),
797
877
  error: payload.error
798
878
  };
799
879
  }
800
880
  case "ai.message.interrupted": {
801
881
  const payload = event.payload;
882
+ const currentGenerationId = state.responseGenerationIds[payload.messageId];
883
+ const targetRequestId = payload.requestId ?? generationRequestId(payload.generationId);
884
+ const ownsRequestPhase = payload.requestId !== void 0 && state.activeGeneration === null && state.activeRequestId === payload.requestId && state.activeResponseMessageId === payload.messageId;
885
+ if (currentGenerationId !== void 0 && !ownsRequestPhase && (payload.generationId !== void 0 ? currentGenerationId !== payload.generationId : generationRequestId(currentGenerationId) !== payload.requestId)) return state;
886
+ if (state.activeGeneration === null && state.activeRequestId !== null && state.activeResponseMessageId === payload.messageId && targetRequestId !== state.activeRequestId) return state;
887
+ const currentTerminal = currentGenerationId === void 0 ? void 0 : terminalGenerationReason(state, currentGenerationId);
888
+ if (currentTerminal === "completed" || currentTerminal === "interrupted" || currentTerminal === "superseded") return state;
802
889
  const active = state.activeGeneration;
803
890
  if (active === null) {
804
- if (payload.generationId === void 0 && state.activeRequestId !== null && state.activeResponseMessageId === payload.messageId) return {
805
- ...state,
806
- status: "idle",
807
- activeRequestId: null,
808
- activeResponseMessageId: null,
809
- error: null
810
- };
811
- if (!(state.pendingApprovals.some((approval) => approval.messageId === payload.messageId) || state.pendingInputs.some((input) => input.messageId === payload.messageId) || state.tools.some((tool) => tool.messageId === payload.messageId && tool.status === "running"))) return state;
891
+ if (payload.requestId !== void 0 && state.activeRequestId !== null && state.activeRequestId === payload.requestId && state.activeResponseMessageId === payload.messageId) {
892
+ const baseState = (currentGenerationId === void 0 ? null : interruptGenerationProjection(state, currentGenerationId, payload.messageId, payload.lastSeenIndex ?? Number.POSITIVE_INFINITY)) ?? state;
893
+ return {
894
+ ...baseState,
895
+ status: "idle",
896
+ activeRequestId: null,
897
+ activeResponseMessageId: null,
898
+ activeProjection: null,
899
+ terminalRequestIds: {
900
+ ...baseState.terminalRequestIds,
901
+ [payload.requestId]: true
902
+ },
903
+ ...currentGenerationId === void 0 ? {} : { terminalGenerations: {
904
+ ...baseState.terminalGenerations,
905
+ [currentGenerationId]: "interrupted"
906
+ } },
907
+ error: null
908
+ };
909
+ }
910
+ const targetGenerationId = payload.generationId ?? (currentGenerationId !== void 0 && generationRequestId(currentGenerationId) === payload.requestId ? currentGenerationId : void 0);
911
+ if (targetGenerationId === void 0) return state;
912
+ if (currentGenerationId !== targetGenerationId) return state;
913
+ const matchesGeneration = (generationId) => targetGenerationId === generationId;
914
+ const hasMessageWork = state.pendingApprovals.some((approval) => approval.messageId === payload.messageId && matchesGeneration(approval.generationId)) || state.pendingInputs.some((input) => input.messageId === payload.messageId && matchesGeneration(input.generationId)) || state.tools.some((activity) => activity.messageId === payload.messageId && activity.status === "running" && matchesGeneration(activity.generationId));
915
+ const resolvesFailedGeneration = state.status === "failed" && currentGenerationId === targetGenerationId && (state.activeRequestId === null || state.activeRequestId === targetRequestId || state.activeRequestId === `ai.generate:tools:${targetGenerationId}`);
916
+ if (!hasMessageWork && !resolvesFailedGeneration && !(currentGenerationId === targetGenerationId && currentTerminal === void 0)) return state;
917
+ const pendingRequestId = state.activeRequestId;
918
+ const projectedState = interruptGenerationProjection(state, targetGenerationId, payload.messageId, payload.lastSeenIndex ?? Number.POSITIVE_INFINITY);
919
+ const baseState = projectedState ?? state;
812
920
  const next = {
813
- ...state,
921
+ ...baseState,
814
922
  activeRequestId: null,
815
923
  activeResponseMessageId: null,
816
- messages: state.messages.map((message) => message.id === payload.messageId ? interruptUIMessage(message) : message),
817
- pendingApprovals: state.pendingApprovals.filter((approval) => approval.messageId !== payload.messageId),
818
- pendingInputs: state.pendingInputs.filter((input) => input.messageId !== payload.messageId),
819
- tools: stopGenerationTools(state.tools, payload.generationId ?? "", payload.messageId, "Tool execution was interrupted.")
924
+ activeProjection: null,
925
+ messages: projectedState === null ? state.messages.map((message) => message.id === payload.messageId ? interruptUIMessage(message) : message) : baseState.messages,
926
+ pendingApprovals: baseState.pendingApprovals.filter((approval) => approval.messageId !== payload.messageId || !matchesGeneration(approval.generationId)),
927
+ pendingInputs: baseState.pendingInputs.filter((input) => input.messageId !== payload.messageId || !matchesGeneration(input.generationId)),
928
+ tools: projectedState === null ? stopGenerationTools(state.tools, targetGenerationId, payload.messageId, "Tool execution was interrupted.") : baseState.tools,
929
+ terminalGenerations: {
930
+ ...baseState.terminalGenerations,
931
+ [targetGenerationId]: "interrupted"
932
+ },
933
+ ...pendingRequestId === null ? {} : { terminalRequestIds: {
934
+ ...baseState.terminalRequestIds,
935
+ [pendingRequestId]: true
936
+ } },
937
+ error: resolvesFailedGeneration ? null : state.error
820
938
  };
821
939
  return {
822
940
  ...next,
823
- status: lifecycleStatus(state, next)
941
+ status: waitingStatus(next)
824
942
  };
825
943
  }
826
- if (payload.generationId === void 0 ? payload.messageId !== active.responseMessageId : payload.generationId !== active.generationId) return state;
827
- const projection = state.activeProjection;
944
+ if (payload.messageId !== active.responseMessageId || payload.generationId !== active.generationId && payload.requestId !== active.requestId) return state;
828
945
  const cutoff = payload.lastSeenIndex ?? Number.POSITIVE_INFINITY;
829
- const retained = projection?.generationId === active.generationId ? {
830
- ...projection,
831
- batches: projection.batches.filter((batch) => batch.index <= cutoff),
832
- toolEvents: projection.toolEvents.filter((toolEvent) => toolEvent.index <= cutoff),
833
- approvalEvents: projection.approvalEvents.filter((approvalEvent) => approvalEvent.index <= cutoff)
834
- } : null;
835
- const shouldProject = retained !== null && (retained.baseMessage !== void 0 || retained.batches.length > 0);
946
+ const projectedState = interruptGenerationProjection(state, active.generationId, active.responseMessageId, cutoff);
947
+ const baseState = projectedState ?? restoreGenerationBase(state, active.generationId, active.responseMessageId, "interrupted");
836
948
  const next = {
837
- ...state,
838
- messages: shouldProject ? upsertMessage(state.messages, interruptUIMessage(projectedMessage(retained))) : state.messages,
839
- activeGeneration: null,
949
+ ...baseState,
840
950
  activeRequestId: null,
841
951
  activeResponseMessageId: null,
842
- activeProjection: null,
843
- pendingApprovals: state.pendingApprovals.filter((approval) => approval.generationId !== active.generationId),
844
- tools: stopGenerationTools(retained === null ? state.tools : projectedTools(retained), active.generationId, active.responseMessageId, "Tool execution was interrupted.")
952
+ terminalRequestIds: {
953
+ ...baseState.terminalRequestIds,
954
+ [active.requestId]: true
955
+ },
956
+ tools: projectedState === null ? stopGenerationTools(baseState.tools, active.generationId, active.responseMessageId, "Tool execution was interrupted.") : baseState.tools
845
957
  };
846
958
  return {
847
959
  ...next,
@@ -851,8 +963,8 @@ function reduceAIState(state, event) {
851
963
  case "ai.tool.called": {
852
964
  const payload = event.payload;
853
965
  const projection = state.activeProjection;
854
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
855
- if (state.tools.some((tool) => tool.generationId === payload.generationId && tool.toolCallId === payload.toolCallId && tool.status === "failed" && state.activeProjection?.generationId !== payload.generationId)) return state;
966
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
967
+ if (state.tools.some((activity) => activity.generationId === payload.generationId && activity.toolCallId === payload.toolCallId && activity.status === "failed" && state.activeGeneration?.generationId !== payload.generationId)) return state;
856
968
  if (projection?.generationId !== payload.generationId) {
857
969
  const next = {
858
970
  ...state,
@@ -874,17 +986,23 @@ function reduceAIState(state, event) {
874
986
  payload
875
987
  }]
876
988
  };
877
- return {
989
+ const next = {
878
990
  ...state,
879
991
  activeProjection,
880
992
  tools: projectedTools(activeProjection)
881
993
  };
994
+ return {
995
+ ...next,
996
+ status: lifecycleStatus(state, next)
997
+ };
882
998
  }
883
999
  case "ai.tool.result": {
884
1000
  const payload = event.payload;
885
1001
  const projection = state.activeProjection;
886
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
887
- if (state.tools.some((tool) => tool.generationId === payload.generationId && tool.toolCallId === payload.toolCallId && tool.status === "failed" && state.activeProjection?.generationId !== payload.generationId)) return state;
1002
+ const terminalReason = terminalGenerationReason(state, payload.generationId);
1003
+ const completesFailedTool = terminalReason === "failed" && state.status === "failed" && payload.preliminary !== true && projection?.generationId === payload.generationId && projectedTools(projection).some((activity) => activity.generationId === payload.generationId && activity.toolCallId === payload.toolCallId && activity.status === "running");
1004
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalReason !== void 0 && !completesFailedTool) return state;
1005
+ if (state.tools.some((activity) => activity.generationId === payload.generationId && activity.toolCallId === payload.toolCallId && activity.status === "failed" && state.activeGeneration?.generationId !== payload.generationId && !completesFailedTool)) return state;
888
1006
  if (projection?.generationId !== payload.generationId) {
889
1007
  const next = {
890
1008
  ...state,
@@ -907,18 +1025,25 @@ function reduceAIState(state, event) {
907
1025
  payload
908
1026
  }]
909
1027
  };
910
- return {
1028
+ const projected = projectedMessage(activeProjection);
1029
+ const tools = projectedTools(activeProjection);
1030
+ const failureError = state.error ?? "Generation failed.";
1031
+ const next = {
911
1032
  ...state,
912
- messages: upsertMessage(state.messages, projectedMessage(activeProjection)),
1033
+ messages: upsertMessage(state.messages, terminalReason === "failed" ? interruptUIMessage(projected, `Generation failed before tool completion: ${failureError}`) : projected),
913
1034
  activeProjection,
914
- tools: projectedTools(activeProjection)
1035
+ tools: terminalReason === "failed" ? stopGenerationTools(tools, payload.generationId, payload.messageId, failureError) : tools
1036
+ };
1037
+ return {
1038
+ ...next,
1039
+ status: lifecycleStatus(state, next)
915
1040
  };
916
1041
  }
917
1042
  case "ai.approval.requested": {
918
1043
  const payload = event.payload;
919
1044
  const projection = state.activeProjection;
920
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
921
- if (state.tools.some((tool) => tool.generationId === payload.generationId && tool.toolCallId === payload.toolCallId && tool.status === "failed" && projection?.generationId !== payload.generationId)) return state;
1045
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
1046
+ if (state.tools.some((activity) => activity.generationId === payload.generationId && activity.toolCallId === payload.toolCallId && activity.status === "failed" && state.activeGeneration?.generationId !== payload.generationId)) return state;
922
1047
  const activeProjection = projection?.generationId === payload.generationId ? {
923
1048
  ...projection,
924
1049
  approvalEvents: [...projection.approvalEvents, {
@@ -940,7 +1065,7 @@ function reduceAIState(state, event) {
940
1065
  }
941
1066
  case "ai.approval.responded": {
942
1067
  const payload = event.payload;
943
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
1068
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
944
1069
  const projection = state.activeProjection;
945
1070
  const pendingRequest = state.pendingApprovals.find((candidate) => candidate.approvalId === payload.approvalId && candidate.messageId === payload.messageId && candidate.generationId === payload.generationId);
946
1071
  const activeRequest = projection?.approvalEvents.findLast((candidate) => candidate.type === "ai.approval.requested" && candidate.payload.approvalId === payload.approvalId && candidate.payload.messageId === payload.messageId && candidate.payload.generationId === payload.generationId);
@@ -966,7 +1091,7 @@ function reduceAIState(state, event) {
966
1091
  }
967
1092
  case "ai.input.requested": {
968
1093
  const payload = event.payload;
969
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
1094
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
970
1095
  const next = {
971
1096
  ...state,
972
1097
  pendingInputs: [...state.pendingInputs.filter((input) => input.messageId !== payload.messageId || input.generationId !== payload.generationId || input.inputId !== payload.inputId), payload]
@@ -978,7 +1103,7 @@ function reduceAIState(state, event) {
978
1103
  }
979
1104
  case "ai.input.responded": {
980
1105
  const payload = event.payload;
981
- if (isStaleGeneration(state, payload.messageId, payload.generationId)) return state;
1106
+ if (isStaleGeneration(state, payload.messageId, payload.generationId) || terminalGenerationReason(state, payload.generationId) !== void 0) return state;
982
1107
  const next = {
983
1108
  ...state,
984
1109
  pendingInputs: state.pendingInputs.filter((input) => input.inputId !== payload.inputId || input.messageId !== payload.messageId || input.generationId !== payload.generationId)
@@ -1022,21 +1147,26 @@ function deriveUIMessages(history) {
1022
1147
  /** Build the standard AI state projection for any contract containing the protocol. */
1023
1148
  function createReducer(options) {
1024
1149
  return options.contract.reducer({
1025
- name: options.name ?? "a2.ai.state.v6",
1150
+ name: options.name ?? "a2.ai.state.v8",
1026
1151
  initialState: initialState()
1027
1152
  }).fold((state, event) => reduceAIState(state, event));
1028
1153
  }
1029
- const seedMessage = (message) => [{
1154
+ const messageInput = (message, generate) => [{
1030
1155
  type: "ai.message.created",
1031
1156
  id: `ai.message:${message.id}`,
1032
- payload: { message }
1157
+ payload: {
1158
+ message,
1159
+ ...generate === false ? { generate: false } : {}
1160
+ }
1033
1161
  }];
1034
1162
  /** Pure append inputs for the built-in AI protocol. */
1035
1163
  const inputs = {
1036
- message(message) {
1037
- return seedMessage(message);
1164
+ message(message, options) {
1165
+ return messageInput(message, options?.generate);
1166
+ },
1167
+ seed(message) {
1168
+ return messageInput(message, false);
1038
1169
  },
1039
- seed: seedMessage,
1040
1170
  approval(response) {
1041
1171
  return [{
1042
1172
  type: "ai.approval.responded",
@@ -1068,12 +1198,27 @@ const inputs = {
1068
1198
  interrupt(interruption) {
1069
1199
  return [{
1070
1200
  type: "ai.message.interrupted",
1071
- id: `ai.interrupt:${interruption.generationId ?? interruption.messageId}`,
1201
+ id: `ai.interrupt:${interruption.generationId ?? interruption.requestId}`,
1072
1202
  payload: interruption
1073
1203
  }];
1074
1204
  }
1075
1205
  };
1076
1206
  /**
1207
+ * Read the durable handler context ambient to the current AI tool
1208
+ * execution. A2 runs each local tool's `execute` inside this scope, so
1209
+ * plain AI SDK `tool()` definitions reach their `event`, `attempt`,
1210
+ * `session`, and `signal` without an A2-specific wrapper. The agent (or
1211
+ * contract) argument carries the event types and is verified against
1212
+ * the executing server's contract.
1213
+ */
1214
+ function handlerContext(agentOrContract) {
1215
+ const contract = "contract" in agentOrContract ? agentOrContract.contract : agentOrContract;
1216
+ const scope = ambientToolScopeStorage()?.getStore();
1217
+ if (scope === void 0) throw new TypeError(`handlerContext('${contract.name}') requires an ambient A2 tool execution: call it inside a local tool's execute, or wrap direct tool tests with runWithHandlerContext`);
1218
+ if (scope.contract !== void 0 && scope.contract.name !== contract.name) throw new TypeError(`handlerContext('${contract.name}') was called during a tool execution for agent '${scope.contract.name}'`);
1219
+ return scope.context;
1220
+ }
1221
+ /**
1077
1222
  * Define an agent: built-in AI protocol + application events and one state
1078
1223
  * reducer. The result is isomorphic.
1079
1224
  */
@@ -1091,10 +1236,10 @@ function agent(options) {
1091
1236
  return {
1092
1237
  contract: agentContract,
1093
1238
  reducer: agentContract.reducer({
1094
- name: options.reducerName ?? "a2.ai.state.v6",
1239
+ name: options.reducerName ?? "a2.ai.state.v8",
1095
1240
  initialState: initialState()
1096
1241
  }).fold((state, event) => reduceAIState(state, event))
1097
1242
  };
1098
1243
  }
1099
1244
  //#endregion
1100
- export { agent, createEvents, createReducer, deriveUIMessages, events, inputs, reduceAIState };
1245
+ export { agent, createEvents, createReducer, deriveUIMessages, events, handlerContext, inputs, reduceAIState };