experimental-a2 0.18.1 → 0.20.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 (143) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/actor.js +1 -1
  3. package/dist/{ai-BMLlI2Ff.d.ts → ai-Cz3hqVhk.d.ts} +33 -16
  4. package/dist/ai-Cz3hqVhk.d.ts.map +1 -0
  5. package/dist/ai-client.d.ts +1 -1
  6. package/dist/ai-client.js +1 -1
  7. package/dist/ai-control-CvLbW10B.js +46 -0
  8. package/dist/ai-control-CvLbW10B.js.map +1 -0
  9. package/dist/ai-fx.d.ts +1 -1
  10. package/dist/ai-fx.d.ts.map +1 -1
  11. package/dist/ai-fx.js +63 -13
  12. package/dist/ai-fx.js.map +1 -1
  13. package/dist/{ai-harness-B2suP2Qh.d.ts → ai-harness-CV6rXbB3.d.ts} +2 -2
  14. package/dist/ai-harness-CV6rXbB3.d.ts.map +1 -0
  15. package/dist/{ai-harness-BuLh_EYi.js → ai-harness-Clbg5ei7.js} +88 -15
  16. package/dist/ai-harness-Clbg5ei7.js.map +1 -0
  17. package/dist/{ai-D6mGlO8j.js → ai-mMiWul1T.js} +116 -77
  18. package/dist/ai-mMiWul1T.js.map +1 -0
  19. package/dist/ai-pi.d.ts +1 -1
  20. package/dist/ai-pi.d.ts.map +1 -1
  21. package/dist/ai-pi.js +56 -11
  22. package/dist/ai-pi.js.map +1 -1
  23. package/dist/ai-react.d.ts +1 -1
  24. package/dist/ai-react.js +1 -1
  25. package/dist/ai-server.d.ts +2 -2
  26. package/dist/ai-server.d.ts.map +1 -1
  27. package/dist/ai-server.js +123 -53
  28. package/dist/ai-server.js.map +1 -1
  29. package/dist/ai.d.ts +2 -2
  30. package/dist/ai.js +1 -1
  31. package/dist/internal-CscSY7K_.js.map +1 -1
  32. package/dist/scheduler-qstash.d.ts.map +1 -1
  33. package/dist/scheduler-qstash.js +5 -5
  34. package/dist/scheduler-qstash.js.map +1 -1
  35. package/dist/scheduler-vercel.d.ts.map +1 -1
  36. package/dist/scheduler-vercel.js +5 -7
  37. package/dist/scheduler-vercel.js.map +1 -1
  38. package/dist/server-DYAATFuT.d.ts.map +1 -1
  39. package/dist/{server-D-Y4pqwu.js → server-qyb5aJJG.js} +39 -25
  40. package/dist/server-qyb5aJJG.js.map +1 -0
  41. package/dist/server.js +1 -1
  42. package/dist/session-clear-DGI4jTQ5.js.map +1 -1
  43. package/dist/store-postgres.d.ts.map +1 -1
  44. package/dist/store-postgres.js +38 -136
  45. package/dist/store-postgres.js.map +1 -1
  46. package/docs/01-quickstart.mdx +7 -4
  47. package/docs/actors/01-introduction.mdx +1 -1
  48. package/docs/actors/meta.ts +1 -1
  49. package/docs/agents/02-tools.mdx +1 -1
  50. package/docs/agents/03-conversations.mdx +26 -14
  51. package/docs/agents/04-harnesses.mdx +51 -5
  52. package/docs/agents/meta.ts +1 -1
  53. package/docs/client/meta.ts +1 -1
  54. package/docs/contract/03-sessions.mdx +2 -2
  55. package/docs/index.mdx +2 -2
  56. package/docs/reference/01-core.mdx +8 -8
  57. package/docs/reference/02-server.mdx +3 -3
  58. package/docs/reference/04-ai.mdx +36 -19
  59. package/docs/reference/05-telemetry.mdx +132 -0
  60. package/docs/reference/06-errors.mdx +1 -1
  61. package/docs/reference/meta.ts +1 -1
  62. package/docs/schedulers/01-overview.mdx +112 -0
  63. package/docs/schedulers/02-vercel-queues.mdx +87 -0
  64. package/docs/schedulers/03-qstash.mdx +123 -0
  65. package/docs/schedulers/meta.ts +1 -0
  66. package/docs/server/01-handlers.mdx +29 -2
  67. package/docs/server/02-timers.mdx +3 -3
  68. package/docs/server/06-guarantees.mdx +6 -3
  69. package/docs/stores/01-overview.mdx +37 -0
  70. package/docs/stores/02-redis.mdx +49 -0
  71. package/docs/stores/03-redis-http.mdx +56 -0
  72. package/docs/stores/04-postgres.mdx +121 -0
  73. package/docs/stores/05-sqlite.mdx +34 -0
  74. package/docs/stores/06-memory.mdx +22 -0
  75. package/docs/stores/meta.ts +1 -0
  76. package/examples/playground/DEPLOY.md +19 -5
  77. package/examples/playground/app/a0/a0.css +244 -5
  78. package/examples/playground/app/a0/app-view.tsx +2 -1
  79. package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
  80. package/examples/playground/app/a0/attachments.ts +133 -0
  81. package/examples/playground/app/a0/chat.test.tsx +595 -0
  82. package/examples/playground/app/a0/chat.tsx +623 -63
  83. package/examples/playground/app/a0/context-menu.tsx +38 -11
  84. package/examples/playground/app/a0/model.test.ts +28 -0
  85. package/examples/playground/app/a0/model.ts +30 -0
  86. package/examples/playground/app/a0/server.ts +5 -0
  87. package/examples/playground/app/a0/shell.tsx +1 -1
  88. package/examples/playground/app/a0/uploads/route.test.ts +59 -0
  89. package/examples/playground/app/a0/uploads/route.ts +29 -0
  90. package/examples/playground/app/agent/[agentId]/agent-client.tsx +2 -1
  91. package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -89
  92. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
  93. package/examples/playground/app/agent/ingress.test.ts +41 -17
  94. package/examples/playground/app/agent/model-input.test.tsx +92 -43
  95. package/examples/playground/app/agent/model.test.ts +12 -3
  96. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +1 -5
  97. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
  98. package/examples/playground/app/chat/server.test.ts +38 -65
  99. package/examples/playground/app/components/activity-history.test.tsx +60 -18
  100. package/examples/playground/app/components/fast-link.test.tsx +3 -5
  101. package/examples/playground/app/initial-state.test.tsx +86 -45
  102. package/examples/playground/next.config.ts +7 -4
  103. package/examples/playground/package.json +4 -1
  104. package/examples/playground/test/agent.ts +35 -0
  105. package/examples/playground/test/model.ts +149 -0
  106. package/examples/playground/test/serve.ts +184 -0
  107. package/examples/playground/test/server-only.ts +1 -0
  108. package/examples/playground/vitest.config.ts +27 -0
  109. package/package.json +3 -1
  110. package/sql/postgres/notifications.sql +24 -0
  111. package/sql/postgres/schema.sql +137 -0
  112. package/src/ai-agent.ts +9 -4
  113. package/src/ai-client-state.ts +21 -9
  114. package/src/ai-context-schema.ts +28 -4
  115. package/src/ai-context.ts +83 -3
  116. package/src/ai-control-server.ts +70 -43
  117. package/src/ai-control-state.ts +3 -4
  118. package/src/ai-control.ts +59 -10
  119. package/src/ai-coordinator.ts +7 -6
  120. package/src/ai-fx.ts +95 -23
  121. package/src/ai-harness.ts +171 -29
  122. package/src/ai-message-shape.ts +41 -0
  123. package/src/ai-pi.ts +88 -32
  124. package/src/ai-stored-state.ts +1 -3
  125. package/src/ai.ts +116 -77
  126. package/src/errors.ts +1 -1
  127. package/src/internal.ts +2 -1
  128. package/src/libfx.d.ts +8 -1
  129. package/src/postgres-notifications.ts +0 -22
  130. package/src/postgres-resources.ts +6 -9
  131. package/src/postgres-schema.ts +80 -0
  132. package/src/scheduler-qstash.ts +12 -5
  133. package/src/scheduler-vercel.ts +11 -16
  134. package/src/server.ts +66 -32
  135. package/src/store-postgres.ts +5 -123
  136. package/dist/ai-BMLlI2Ff.d.ts.map +0 -1
  137. package/dist/ai-D6mGlO8j.js.map +0 -1
  138. package/dist/ai-harness-B2suP2Qh.d.ts.map +0 -1
  139. package/dist/ai-harness-BuLh_EYi.js.map +0 -1
  140. package/dist/server-D-Y4pqwu.js.map +0 -1
  141. package/docs/reference/05-adapters.mdx +0 -351
  142. package/docs/server/05-production.mdx +0 -323
  143. package/examples/playground/app/a0/no-photon.cjs +0 -4
package/dist/ai-server.js CHANGED
@@ -2,11 +2,11 @@ import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
2
  import { a as reducer, s as validateSync } from "./reducer-BAXc8JyF.js";
3
3
  import { h as installAmbientToolScopeStorage, m as failedPreviousAttempt, p as consumeSchedulerSendFailure } from "./internal-CscSY7K_.js";
4
4
  import { t as boundedCache } from "./bounded-cache-CPHWF-8h.js";
5
- import { a as CONSOLE_TELEMETRY, o as setServerFetchHooks, t as createServer } from "./server-D-Y4pqwu.js";
6
- import { A as interruptUIMessage, C as reduceToolActivity, D as flattenProgressBatches, E as upsertResponse, M as assertMessageBase, N as restoreMessageBase, O as progressBatches, S as projectedMessage, T as upsertMessage, _ as applyControlChanges, a as events, b as findMessage, d as appSlice, f as isAgentContract, g as initialControlState, h as aiEventId, j as pauseUIMessage, k as progressOwnedBy, m as AI_EVENT_ID_PREFIX, n as agentContract, p as withAIStorage, u as appReducerOf, v as controlCommit, w as retainMessageBase, y as advanceProjection } from "./ai-D6mGlO8j.js";
7
- import { a as harnessDriver, o as harnessLog, s as harnessStates, u as registerHarnessStates } from "./ai-harness-BuLh_EYi.js";
5
+ import { a as CONSOLE_TELEMETRY, o as setServerFetchHooks, t as createServer } from "./server-qyb5aJJG.js";
6
+ import { a as unexplainedAdmission, i as steersOn, n as initialControlState, r as steeringEnd, t as STEERED_REASON } from "./ai-control-CvLbW10B.js";
7
+ import { A as interruptUIMessage, C as reduceToolActivity, D as flattenProgressBatches, E as upsertResponse, M as assertMessageBase, N as restoreMessageBase, O as progressBatches, S as projectedMessage, T as upsertMessage, _ as applyControlChanges, a as events, b as findMessage, d as appSlice, f as isAgentContract, g as isUIMessage, h as aiEventId, j as pauseUIMessage, k as progressOwnedBy, m as AI_EVENT_ID_PREFIX, n as agentContract, p as withAIStorage, u as appReducerOf, v as controlCommit, w as retainMessageBase, y as advanceProjection } from "./ai-mMiWul1T.js";
8
+ import { a as gatewaySessionAffinity, c as harnessLog, f as registerHarnessStates, l as harnessStates, o as harnessDriver } from "./ai-harness-Clbg5ei7.js";
8
9
  import { AsyncLocalStorage } from "node:async_hooks";
9
- import { createHash } from "node:crypto";
10
10
  import { asSchema, convertToModelMessages, isToolUIPart, stepCountIs, streamText, toUIMessageStream } from "ai";
11
11
  import { Buffer } from "node:buffer";
12
12
  //#region src/ai-coordinator.ts
@@ -43,12 +43,11 @@ const foldCoordinator = (state, event) => {
43
43
  }
44
44
  case "ai.message.created": {
45
45
  const payload = event.payload;
46
- const message = payload.message;
47
- if (message.role !== "user") return state;
46
+ if (payload.role !== "user") return state;
48
47
  return {
49
48
  ...state,
50
- queued: [...state.queued.filter((item) => item.messageId !== message.id), {
51
- messageId: message.id,
49
+ queued: [...state.queued.filter((item) => item.messageId !== payload.messageId), {
50
+ messageId: payload.messageId,
52
51
  generate: payload.generate !== false
53
52
  }]
54
53
  };
@@ -244,7 +243,7 @@ const createControlRuntime = (options) => {
244
243
  const telemetry = options.telemetry ?? CONSOLE_TELEMETRY;
245
244
  const reducer$2 = reducer({
246
245
  contract: options.contract,
247
- name: "a2.ai.control.v5",
246
+ name: "a2.ai.control.v6",
248
247
  initialState: initialControlState()
249
248
  }).fold((state, event) => {
250
249
  if (event.type === "ai.control.requested") {
@@ -309,15 +308,41 @@ const createControlRuntime = (options) => {
309
308
  });
310
309
  state.active = null;
311
310
  };
312
- const insert = ({ message, generate = true, first = false, afterStepOf }) => {
311
+ const decide = async () => {
312
+ const receipt = {
313
+ commandId: ctx.event.id,
314
+ ...ctx.event.type === "ai.work.reported" ? { work: true } : {},
315
+ outcome: reason === void 0 ? "applied" : "rejected",
316
+ ...reason === void 0 ? {} : { reason }
317
+ };
318
+ return [
319
+ ...facts,
320
+ {
321
+ type: "ai.control.committed",
322
+ id: await aiEventId("a2.ai.control.committed", ctx.event.id),
323
+ payload: controlCommit({
324
+ before,
325
+ after: state,
326
+ moves
327
+ })
328
+ },
329
+ ...jobs,
330
+ {
331
+ type: "ai.control.decided",
332
+ id: await aiEventId("a2.ai.control.decided", ctx.event.id),
333
+ payload: receipt
334
+ }
335
+ ];
336
+ };
337
+ const insert = ({ message, generate = true, afterStepOf }) => {
313
338
  const candidate = {
314
339
  id: message.id,
315
- message,
340
+ role: message.role,
316
341
  generate: generate && message.role === "user",
317
342
  ...afterStepOf === void 0 ? {} : { afterStepOf }
318
343
  };
319
- if (first) state.inbox.items.unshift(candidate);
320
- else state.inbox.items.push(candidate);
344
+ if (afterStepOf === void 0) state.inbox.items.push(candidate);
345
+ else state.inbox.items.splice(steeringEnd(state.inbox.items, afterStepOf), 0, candidate);
321
346
  };
322
347
  const moveInput = ({ from, to }) => {
323
348
  if (from === to) return;
@@ -380,9 +405,8 @@ const createControlRuntime = (options) => {
380
405
  case "edit": {
381
406
  const item = state.inbox.items.find((candidate) => candidate.id === command.message.id);
382
407
  if (item === void 0) reason = "not-pending";
383
- else if (item.message.role !== command.message.role) reason = "role-mismatch";
384
- else item.message = command.message;
385
- break;
408
+ else if (item.role !== command.message.role) reason = "role-mismatch";
409
+ return decide();
386
410
  }
387
411
  case "remove": {
388
412
  const position = state.inbox.items.findIndex((item) => item.id === command.inputId);
@@ -408,14 +432,15 @@ const createControlRuntime = (options) => {
408
432
  const selected = state.inbox.items[position];
409
433
  if (!selected) reason = "not-pending";
410
434
  else if ((state.active?.turnId ?? null) !== command.turnId) reason = "stale-turn";
411
- else if (selected.message.role !== "user") reason = "not-user-input";
435
+ else if (selected.role !== "user") reason = "not-user-input";
412
436
  else {
413
437
  selected.generate = true;
414
438
  if (state.active) selected.afterStepOf = state.active.turnId;
415
439
  else delete selected.afterStepOf;
440
+ const others = state.inbox.items.filter((item) => item !== selected);
416
441
  moveInput({
417
442
  from: position,
418
- to: 0
443
+ to: state.active ? steeringEnd(others, state.active.turnId) : 0
419
444
  });
420
445
  }
421
446
  break;
@@ -431,7 +456,6 @@ const createControlRuntime = (options) => {
431
456
  insert({
432
457
  message: command.message,
433
458
  generate: true,
434
- first: true,
435
459
  afterStepOf: command.turnId
436
460
  });
437
461
  break;
@@ -597,12 +621,12 @@ const createControlRuntime = (options) => {
597
621
  const modelDone = active.model === null || active.model.settled;
598
622
  const workSettled = modelDone && response.calls.every((call) => call.work === void 0 || call.work.settled);
599
623
  const externalPending = response.calls.some((call) => call.call.providerExecuted && call.call.supportsDeferredResults && !call.terminal && !approvalBlocksExecution(call));
600
- const handoffReady = handoff && workSettled && !externalPending && (active.suspended || response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
624
+ const handoffReady = handoff && workSettled && !externalPending && (active.suspended ? !state.inbox.paused : response.failure !== void 0 || response.calls.every((call) => callReadyForModel(call) || approvalBlocksExecution(call)));
601
625
  const readyForModel = !active.suspended && response.failure === void 0 && modelDone && callsDone && response.inputs.length === 0;
602
626
  const naturalCompletion = readyForModel && response.completion !== void 0 && response.completion.finishReason !== "tool-calls";
603
627
  if (handoffReady && !naturalCompletion) await end({
604
628
  lastSeenIndex: void 0,
605
- reason: "steered after step"
629
+ reason: STEERED_REASON
606
630
  });
607
631
  else if (readyForModel && (!handoff || handoffReady)) {
608
632
  const resume = active.model !== null && active.model.version !== active.version;
@@ -632,12 +656,14 @@ const createControlRuntime = (options) => {
632
656
  }
633
657
  if (state.active === null && !state.inbox.paused && !state.coordinator.closed) while (state.inbox.items.length > 0) {
634
658
  const item = state.inbox.items.shift();
659
+ if (steersOn(item, state.inbox.items)) item.generate = false;
635
660
  delete item.afterStepOf;
636
661
  await emit({
637
662
  type: "ai.message.created",
638
663
  payload: {
639
- message: item.message,
640
- generate: item.generate
664
+ messageId: item.id,
665
+ role: item.role,
666
+ ...item.generate ? {} : { generate: false }
641
667
  },
642
668
  id: await aiEventId("a2.ai.admitted", options.contract.name, ctx.event.sessionId, item.id)
643
669
  });
@@ -661,30 +687,7 @@ const createControlRuntime = (options) => {
661
687
  });
662
688
  break;
663
689
  }
664
- const receipt = {
665
- commandId: ctx.event.id,
666
- ...ctx.event.type === "ai.work.reported" ? { work: true } : {},
667
- outcome: reason === void 0 ? "applied" : "rejected",
668
- ...reason === void 0 ? {} : { reason }
669
- };
670
- return [
671
- ...facts,
672
- {
673
- type: "ai.control.committed",
674
- id: await aiEventId("a2.ai.control.committed", ctx.event.id),
675
- payload: controlCommit({
676
- before,
677
- after: state,
678
- moves
679
- })
680
- },
681
- ...jobs,
682
- {
683
- type: "ai.control.decided",
684
- id: await aiEventId("a2.ai.control.decided", ctx.event.id),
685
- payload: receipt
686
- }
687
- ];
690
+ return decide();
688
691
  };
689
692
  const report = async ({ ctx, events, settled, name }) => {
690
693
  const payload = ctx.event.payload;
@@ -784,8 +787,12 @@ const index = (value) => typeof value === "number" && Number.isSafeInteger(value
784
787
  const id = (value) => typeof value === "string" && value.length > 0;
785
788
  const nullableId = (value) => value === null || id(value);
786
789
  const matches = (schema, value) => validateSync(schema, value, "AI context checkpoint").issues === void 0;
787
- const isMessage = (value) => matches(events["ai.message.created"], { message: value });
788
790
  const indexes = (value, messages) => record(value) && Object.values(value).every(index) && messages.every((message) => Object.hasOwn(value, message.id));
791
+ const inputs = (value) => record(value) && Object.keys(value).length === 2 && Array.isArray(value["commands"]) && value["commands"].every((held) => record(held) && Object.keys(held).every((key) => [
792
+ "commandId",
793
+ "inputId",
794
+ "message"
795
+ ].includes(key)) && id(held["commandId"]) && id(held["inputId"]) && (held["message"] === void 0 || isUIMessage(held["message"]))) && Array.isArray(value["waiting"]) && value["waiting"].every((input) => record(input) && Object.keys(input).length === 2 && id(input["inputId"]) && isUIMessage(input["message"]));
789
796
  const compaction = (value) => value === null || record(value) && Object.keys(value).every((key) => [
790
797
  "status",
791
798
  "generationId",
@@ -816,7 +823,7 @@ const progress = (value) => {
816
823
  const indexedEvents = (value, types) => Array.isArray(value) && value.every((event) => record(event) && index(event["index"]) && types.includes(event["type"]) && matches(events[event["type"]], event["payload"]));
817
824
  const restorableBase = (options) => {
818
825
  assertMessageBase(options.base);
819
- return isMessage(restoreMessageBase({
826
+ return isUIMessage(restoreMessageBase({
820
827
  current: options.current,
821
828
  base: options.base
822
829
  }));
@@ -880,6 +887,7 @@ const contextSchema = (app) => ({ "~standard": {
880
887
  "compaction",
881
888
  "modelMetadata",
882
889
  "closed",
890
+ "inputs",
883
891
  "authority",
884
892
  "generation",
885
893
  "activeProjection",
@@ -892,7 +900,7 @@ const contextSchema = (app) => ({ "~standard": {
892
900
  const metadata = value["modelMetadata"];
893
901
  const generation = value["generation"];
894
902
  const current = value["activeProjection"];
895
- if (!Array.isArray(messages) || !messages.every(isMessage) || !compaction(value["compaction"]) || typeof value["closed"] !== "boolean" || !authority(value["authority"]) || !record(metadata) || !Object.entries(metadata).every(([modelId, entry]) => record(entry) && (entry["status"] === "pending" || entry["status"] === "unavailable" || entry["status"] === "resolved" && matches(events["ai.model.metadata.resolved"], {
903
+ if (!Array.isArray(messages) || !messages.every(isUIMessage) || !compaction(value["compaction"]) || typeof value["closed"] !== "boolean" || !inputs(value["inputs"]) || !authority(value["authority"]) || !record(metadata) || !Object.entries(metadata).every(([modelId, entry]) => record(entry) && (entry["status"] === "pending" || entry["status"] === "unavailable" || entry["status"] === "resolved" && matches(events["ai.model.metadata.resolved"], {
896
904
  modelId,
897
905
  limits: entry["limits"]
898
906
  }))) || !indexes(value["messageIndexes"], messages)) return invalid;
@@ -1002,6 +1010,10 @@ const createContextReducer = ({ contract }) => {
1002
1010
  compaction: null,
1003
1011
  modelMetadata: {},
1004
1012
  closed: false,
1013
+ inputs: {
1014
+ commands: [],
1015
+ waiting: []
1016
+ },
1005
1017
  authority: null,
1006
1018
  generation: null,
1007
1019
  activeProjection: null,
@@ -1010,7 +1022,7 @@ const createContextReducer = ({ contract }) => {
1010
1022
  };
1011
1023
  const reducer$1 = reducer({
1012
1024
  contract,
1013
- name: app === void 0 ? "a2.ai.context.v6" : `${app.name}+a2.ai.context.v6`,
1025
+ name: app === void 0 ? "a2.ai.context.v7" : `${app.name}+a2.ai.context.v7`,
1014
1026
  initialState,
1015
1027
  stateSchema: contextSchema(app)
1016
1028
  }).fold((state, event) => {
@@ -1046,15 +1058,69 @@ const reduceContext = (state, event) => {
1046
1058
  };
1047
1059
  return view.closed ? endGeneration(next) : next;
1048
1060
  }
1061
+ case "ai.control.requested": {
1062
+ const command = event.payload;
1063
+ const held = command.action === "send" || command.action === "steer" || command.action === "edit" ? {
1064
+ commandId: event.id,
1065
+ inputId: command.message.id,
1066
+ message: command.message
1067
+ } : command.action === "remove" ? {
1068
+ commandId: event.id,
1069
+ inputId: command.inputId
1070
+ } : void 0;
1071
+ if (held === void 0) return state;
1072
+ return {
1073
+ ...state,
1074
+ inputs: {
1075
+ ...state.inputs,
1076
+ commands: [...state.inputs.commands, held]
1077
+ }
1078
+ };
1079
+ }
1080
+ case "ai.control.decided": {
1081
+ const receipt = event.payload;
1082
+ const held = receipt.work ? void 0 : state.inputs.commands.find((command) => command.commandId === receipt.commandId);
1083
+ if (held === void 0) return state;
1084
+ const commands = state.inputs.commands.filter((command) => command !== held);
1085
+ if (receipt.outcome !== "applied") return {
1086
+ ...state,
1087
+ inputs: {
1088
+ ...state.inputs,
1089
+ commands
1090
+ }
1091
+ };
1092
+ const waiting = state.inputs.waiting.filter((input) => input.inputId !== held.inputId);
1093
+ if (held.message !== void 0) waiting.push({
1094
+ inputId: held.inputId,
1095
+ message: held.message
1096
+ });
1097
+ return {
1098
+ ...state,
1099
+ inputs: {
1100
+ commands,
1101
+ waiting
1102
+ }
1103
+ };
1104
+ }
1049
1105
  case "ai.session.closed": return endGeneration({
1050
1106
  ...state,
1051
1107
  closed: true,
1108
+ inputs: {
1109
+ commands: [],
1110
+ waiting: []
1111
+ },
1052
1112
  compaction: state.compaction?.status === "running" ? state.activeProjection?.baseCompaction ?? null : state.compaction
1053
1113
  });
1054
1114
  case "ai.message.created": {
1055
- const { message } = event.payload;
1115
+ const { messageId } = event.payload;
1116
+ const message = state.inputs.waiting.find((input) => input.inputId === messageId)?.message ?? state.inputs.commands.find((command) => command.commandId === messageId)?.message;
1117
+ if (message === void 0) throw unexplainedAdmission(messageId);
1056
1118
  return {
1057
1119
  ...state,
1120
+ inputs: {
1121
+ commands: state.inputs.commands.filter((command) => command.commandId !== messageId),
1122
+ waiting: state.inputs.waiting.filter((input) => input.inputId !== messageId)
1123
+ },
1058
1124
  messages: upsertMessage(state.messages, message),
1059
1125
  activeProjection: state.activeProjection?.responseMessageId === message.id ? {
1060
1126
  ...state.activeProjection,
@@ -1669,7 +1735,10 @@ async function generateWithAISDK(options) {
1669
1735
  ...context.generation,
1670
1736
  headers: Object.keys(headers).some((key) => key.toLowerCase() === "x-session-affinity") ? headers : {
1671
1737
  ...headers,
1672
- "x-session-affinity": createHash("sha256").update(JSON.stringify([agentName, context.event.sessionId])).digest("hex")
1738
+ "x-session-affinity": gatewaySessionAffinity({
1739
+ agentName,
1740
+ sessionId: context.event.sessionId
1741
+ })
1673
1742
  },
1674
1743
  providerOptions: {
1675
1744
  ...context.generation.providerOptions,
@@ -2062,6 +2131,7 @@ function createHandlers(options) {
2062
2131
  if (harness !== void 0) for (const [name, tool] of Object.entries(options.tools ?? {})) {
2063
2132
  const needsApproval = tool.needsApproval;
2064
2133
  if (needsApproval !== void 0 && needsApproval !== false) throw new TypeError(`a harness runs tools without approval; '${name}' sets needsApproval`);
2134
+ if (tool.type === "provider") throw new TypeError(`a harness runs only tools A2 executes; '${name}' is provider-defined`);
2065
2135
  }
2066
2136
  const configuredCompaction = loop.compaction ?? (loop.generate === void 0 && harness === void 0 ? {} : false);
2067
2137
  const staticCompaction = typeof configuredCompaction === "function" ? void 0 : validateCompaction({