experimental-a2 0.15.0 → 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.
Files changed (101) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/actor-client.d.ts +1 -1
  3. package/dist/{actor-BfQSE0KC.d.ts → actor-ohPC-81x.d.ts} +5 -5
  4. package/dist/{actor-BfQSE0KC.d.ts.map → actor-ohPC-81x.d.ts.map} +1 -1
  5. package/dist/actor-react.d.ts +3 -3
  6. package/dist/actor.d.ts +1 -1
  7. package/dist/actor.js +1 -1
  8. package/dist/ai-CrLYwNEx.js +2431 -0
  9. package/dist/ai-CrLYwNEx.js.map +1 -0
  10. package/dist/{ai-Cai-lCbj.d.ts → ai-HJ9fHfYI.d.ts} +72 -13
  11. package/dist/ai-HJ9fHfYI.d.ts.map +1 -0
  12. package/dist/ai-server.d.ts +4 -5
  13. package/dist/ai-server.d.ts.map +1 -1
  14. package/dist/ai-server.js +755 -290
  15. package/dist/ai-server.js.map +1 -1
  16. package/dist/ai.d.ts +2 -2
  17. package/dist/ai.js +1 -1999
  18. package/dist/client-BYzHjkwU.js.map +1 -1
  19. package/dist/{client-BAEABRZB.d.ts → client-CzyacQpJ.d.ts} +9 -9
  20. package/dist/client-CzyacQpJ.d.ts.map +1 -0
  21. package/dist/client.d.ts +1 -1
  22. package/dist/idempotent-id-BRJVeylj.js +13 -0
  23. package/dist/idempotent-id-BRJVeylj.js.map +1 -0
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +1 -12
  26. package/dist/otel.d.ts +1 -1
  27. package/dist/react.d.ts +13 -13
  28. package/dist/react.d.ts.map +1 -1
  29. package/dist/react.js.map +1 -1
  30. package/dist/reducer-DEMjEY_O.js.map +1 -1
  31. package/dist/{reducer-BcS9VDKC.d.ts → reducer-otzHjuJj.d.ts} +3 -3
  32. package/dist/{reducer-BcS9VDKC.d.ts.map → reducer-otzHjuJj.d.ts.map} +1 -1
  33. package/dist/scheduler-qstash.d.ts +2 -2
  34. package/dist/scheduler-qstash.js +1 -1
  35. package/dist/scheduler-vercel.d.ts +2 -2
  36. package/dist/scheduler-vercel.js +1 -1
  37. package/dist/{server-CjJSGcF7.js → server-BD5ckxZb.js} +39 -26
  38. package/dist/server-BD5ckxZb.js.map +1 -0
  39. package/dist/{server-Bp5Nd1pF.d.ts → server-DbD7IJVK.d.ts} +5 -5
  40. package/dist/{server-Bp5Nd1pF.d.ts.map → server-DbD7IJVK.d.ts.map} +1 -1
  41. package/dist/server.d.ts +3 -3
  42. package/dist/server.js +1 -1
  43. package/dist/{store-D_yhNdPz.d.ts → store-DyZM6fS5.d.ts} +3 -2
  44. package/dist/{store-D_yhNdPz.d.ts.map → store-DyZM6fS5.d.ts.map} +1 -1
  45. package/dist/store-N8PXxDAS.js.map +1 -1
  46. package/dist/store-memory.d.ts +1 -1
  47. package/dist/store-postgres.d.ts +1 -1
  48. package/dist/store-redis-http.d.ts +1 -1
  49. package/dist/store-redis-http.js +1 -1
  50. package/dist/{store-redis-notify-D2EI6gwX.js → store-redis-notify-BVLUfI3j.js} +14 -13
  51. package/dist/store-redis-notify-BVLUfI3j.js.map +1 -0
  52. package/dist/store-redis.d.ts +1 -1
  53. package/dist/store-redis.js +1 -1
  54. package/dist/store-sqlite.d.ts +1 -1
  55. package/dist/{telemetry-CpeclqB2.d.ts → telemetry-B5jzpy6y.d.ts} +2 -2
  56. package/dist/telemetry-B5jzpy6y.d.ts.map +1 -0
  57. package/docs/concepts/04-state.mdx +9 -4
  58. package/docs/guides/06-ai-agents.mdx +134 -43
  59. package/docs/reference/01-api.mdx +104 -27
  60. package/examples/playground/app/agent/[agentId]/agent-client.tsx +20 -28
  61. package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +123 -0
  62. package/examples/playground/app/agent/[agentId]/agent-queue.tsx +37 -43
  63. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +44 -24
  64. package/examples/playground/app/agent/compaction-timeline.test.ts +47 -7
  65. package/examples/playground/app/agent/compaction-timeline.ts +13 -3
  66. package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -5
  67. package/examples/playground/app/chat/[chatId]/session.ts +17 -2
  68. package/examples/playground/app/chat/model.test.ts +4 -4
  69. package/examples/playground/app/chat/model.ts +18 -6
  70. package/examples/playground/app/chat/server.ts +6 -3
  71. package/examples/playground/package.json +3 -1
  72. package/package.json +1 -1
  73. package/src/ai-client-state.ts +0 -2
  74. package/src/ai-context-schema.ts +296 -0
  75. package/src/ai-context.ts +680 -0
  76. package/src/ai-control-server.ts +160 -131
  77. package/src/ai-control-state.ts +6 -0
  78. package/src/ai-control.ts +6 -7
  79. package/src/ai-coordinator.ts +92 -38
  80. package/src/ai-id.ts +6 -0
  81. package/src/ai-input-tokens.ts +75 -0
  82. package/src/ai-message-projection.ts +348 -0
  83. package/src/ai-server.ts +232 -334
  84. package/src/ai-stored-state.ts +484 -0
  85. package/src/ai.ts +252 -418
  86. package/src/client.ts +21 -15
  87. package/src/react.ts +25 -19
  88. package/src/reducer.ts +2 -1
  89. package/src/server.ts +73 -56
  90. package/src/store-redis-core.ts +15 -17
  91. package/src/store.ts +1 -0
  92. package/src/telemetry.ts +2 -1
  93. package/dist/ai-Cai-lCbj.d.ts.map +0 -1
  94. package/dist/ai-control-CcD4hh3y.js +0 -119
  95. package/dist/ai-control-CcD4hh3y.js.map +0 -1
  96. package/dist/ai.js.map +0 -1
  97. package/dist/client-BAEABRZB.d.ts.map +0 -1
  98. package/dist/index.js.map +0 -1
  99. package/dist/server-CjJSGcF7.js.map +0 -1
  100. package/dist/store-redis-notify-D2EI6gwX.js.map +0 -1
  101. package/dist/telemetry-CpeclqB2.d.ts.map +0 -1
@@ -8,8 +8,10 @@ import type {
8
8
  GenerationCompletedPayload,
9
9
  GenerationFailedPayload,
10
10
  GenerationRequestedPayload,
11
+ GenerationReason,
11
12
  GenerationStartedPayload,
12
13
  InputRequestedPayload,
14
+ InputRespondedPayload,
13
15
  MessageCreatedPayload,
14
16
  MessageInterruptedPayload,
15
17
  ToolCalledPayload,
@@ -18,12 +20,51 @@ import type {
18
20
  import type { Contract, ContractEvent, EventDefs } from './contract.ts'
19
21
  import type { Reducer } from './reducer.ts'
20
22
 
23
+ type CompletedCall = Pick<
24
+ ToolCalledPayload,
25
+ | 'requestId'
26
+ | 'generationId'
27
+ | 'messageId'
28
+ | 'toolCallId'
29
+ | 'toolName'
30
+ | 'providerExecuted'
31
+ | 'supportsDeferredResults'
32
+ >
33
+
21
34
  export type CoordinatorCall = {
22
- call: ToolCalledPayload
23
35
  approval?: ApprovalRequestedPayload
24
36
  response?: ApprovalRespondedPayload
25
- terminal: boolean
26
37
  work?: ControlWork
38
+ } & (
39
+ | { call: ToolCalledPayload; terminal: false }
40
+ | { call: CompletedCall; terminal: true }
41
+ )
42
+
43
+ const completeCall = (candidate: CoordinatorCall): CoordinatorCall => {
44
+ const {
45
+ requestId,
46
+ generationId,
47
+ messageId,
48
+ toolCallId,
49
+ toolName,
50
+ providerExecuted,
51
+ supportsDeferredResults,
52
+ } = candidate.call
53
+ return {
54
+ terminal: true,
55
+ call: {
56
+ requestId,
57
+ generationId,
58
+ messageId,
59
+ toolCallId,
60
+ toolName,
61
+ ...(providerExecuted === undefined ? {} : { providerExecuted }),
62
+ ...(supportsDeferredResults === undefined
63
+ ? {}
64
+ : { supportsDeferredResults }),
65
+ },
66
+ ...(candidate.work === undefined ? {} : { work: candidate.work }),
67
+ }
27
68
  }
28
69
 
29
70
  export type CoordinatorResponse = {
@@ -32,13 +73,11 @@ export type CoordinatorResponse = {
32
73
  status: 'requested' | 'generating' | 'waiting' | 'failed'
33
74
  stepCount: number
34
75
  activeRequestId?: string
76
+ requestReason: GenerationReason
77
+ sourceGenerationId?: string
35
78
  generation?: GenerationStartedPayload
36
- promptThroughIndex?: number
37
79
  source?: {
38
80
  generation: GenerationStartedPayload
39
- promptThroughIndex: number
40
- failed: boolean
41
- canCompact: boolean
42
81
  }
43
82
  completion?: GenerationCompletedPayload
44
83
  failure?: GenerationFailedPayload
@@ -53,6 +92,7 @@ export type CoordinatorResponse = {
53
92
 
54
93
  export type AICoordinatorState = {
55
94
  closed: boolean
95
+ unsettledTools?: Record<string, true>
56
96
  queued: Array<{ messageId: string; generate: boolean }>
57
97
  response?: CoordinatorResponse
58
98
  calibration?: { model: string; inputTokens: number; estimate: number }
@@ -96,8 +136,7 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
96
136
  case 'ai.generation.requested': {
97
137
  const request = event.payload as GenerationRequestedPayload
98
138
  if (request.reason === 'message') {
99
- const responseMessageId =
100
- request.responseMessageId ?? `${request.messageId}:assistant`
139
+ const responseMessageId = request.responseMessageId
101
140
  const requested = state.queued.findIndex(
102
141
  (item) => item.messageId === request.messageId,
103
142
  )
@@ -115,6 +154,10 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
115
154
  status: 'requested',
116
155
  stepCount: 0,
117
156
  activeRequestId: event.id,
157
+ requestReason: request.reason,
158
+ ...(request.sourceGenerationId === undefined
159
+ ? {}
160
+ : { sourceGenerationId: request.sourceGenerationId }),
118
161
  calls: [],
119
162
  inputs: [],
120
163
  },
@@ -129,8 +172,12 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
129
172
  if (
130
173
  (request.reason === 'tool' &&
131
174
  (!continuationReady(state) ||
132
- event.id !==
133
- `ai.generate:tools:${state.response.generation?.generationId}`)) ||
175
+ request.sourceGenerationId !==
176
+ state.response.generation?.generationId)) ||
177
+ (request.sourceGenerationId !== undefined &&
178
+ request.sourceGenerationId !==
179
+ (state.response.generation ?? state.response.source?.generation)
180
+ ?.generationId) ||
134
181
  (request.reason === 'retry' && state.response.status !== 'failed') ||
135
182
  (request.reason === 'input' &&
136
183
  state.response.inputResponse === undefined)
@@ -139,23 +186,21 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
139
186
  }
140
187
  return updateResponse(state, (response) => {
141
188
  const generation = response.generation ?? response.source?.generation
142
- const promptThroughIndex =
143
- response.promptThroughIndex ?? response.source?.promptThroughIndex
144
189
  return {
145
190
  rootMessageId: response.rootMessageId,
146
191
  responseMessageId: response.responseMessageId,
147
192
  status: 'requested',
148
193
  stepCount: response.stepCount,
149
194
  activeRequestId: event.id,
150
- ...(generation === undefined || promptThroughIndex === undefined
195
+ requestReason: request.reason,
196
+ ...(request.sourceGenerationId === undefined
197
+ ? {}
198
+ : { sourceGenerationId: request.sourceGenerationId }),
199
+ ...(generation === undefined
151
200
  ? {}
152
201
  : {
153
202
  source: {
154
203
  generation,
155
- promptThroughIndex,
156
- failed:
157
- response.failure?.generationId === generation.generationId,
158
- canCompact: response.calls.every((call) => call.terminal),
159
204
  },
160
205
  }),
161
206
  calls: [],
@@ -179,11 +224,12 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
179
224
  status: 'generating',
180
225
  stepCount: response.stepCount,
181
226
  generation,
182
- ...(generation.promptThroughIndex === undefined
183
- ? {}
184
- : { promptThroughIndex: generation.promptThroughIndex }),
185
227
  ...(response.source === undefined ? {} : { source: response.source }),
186
228
  activeRequestId: generation.requestId,
229
+ requestReason: response.requestReason,
230
+ ...(response.sourceGenerationId === undefined
231
+ ? {}
232
+ : { sourceGenerationId: response.sourceGenerationId }),
187
233
  calls: [],
188
234
  inputs: [],
189
235
  }))
@@ -191,7 +237,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
191
237
  case 'ai.generation.completed': {
192
238
  const completion = event.payload as GenerationCompletedPayload
193
239
  if (
194
- state.response?.generation?.generationId !== completion.generationId
240
+ state.response?.generation?.requestId !== completion.requestId ||
241
+ state.response.generation.generationId !== completion.generationId
195
242
  ) {
196
243
  return state
197
244
  }
@@ -223,11 +270,13 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
223
270
  const failure = event.payload as GenerationFailedPayload
224
271
  const response = state.response
225
272
  const ownsActiveGeneration =
226
- response?.generation?.generationId === failure.generationId
273
+ response?.generation?.generationId === failure.generationId &&
274
+ response.generation.requestId === failure.requestId
227
275
  const ownsActiveStepLimit =
228
276
  failure.stepLimit === true &&
229
- response?.activeRequestId ===
230
- `ai.generate:tools:${failure.generationId}` &&
277
+ response?.requestReason === 'tool' &&
278
+ response.sourceGenerationId === failure.generationId &&
279
+ response.source?.generation.requestId === failure.requestId &&
231
280
  response.responseMessageId === failure.responseMessageId
232
281
  if (!ownsActiveGeneration && !ownsActiveStepLimit) {
233
282
  return state
@@ -258,7 +307,10 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
258
307
  }
259
308
  case 'ai.tool.called': {
260
309
  const call = event.payload as ToolCalledPayload
261
- if (state.response?.generation?.generationId !== call.generationId) {
310
+ if (
311
+ state.response?.generation?.requestId !== call.requestId ||
312
+ state.response.generation.generationId !== call.generationId
313
+ ) {
262
314
  return state
263
315
  }
264
316
  return updateResponse(state, (response) => ({
@@ -273,7 +325,10 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
273
325
  }
274
326
  case 'ai.approval.requested': {
275
327
  const approval = event.payload as ApprovalRequestedPayload
276
- if (state.response?.generation?.generationId !== approval.generationId) {
328
+ if (
329
+ state.response?.generation?.requestId !== approval.requestId ||
330
+ state.response.generation.generationId !== approval.generationId
331
+ ) {
277
332
  return state
278
333
  }
279
334
  return updateResponse(state, (response) => ({
@@ -289,7 +344,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
289
344
  const approval = event.payload as ApprovalRespondedPayload
290
345
  if (
291
346
  state.response?.responseMessageId !== approval.messageId ||
292
- state.response.generation?.generationId !== approval.generationId
347
+ state.response.generation?.requestId !== approval.requestId ||
348
+ state.response.generation.generationId !== approval.generationId
293
349
  ) {
294
350
  return state
295
351
  }
@@ -306,7 +362,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
306
362
  const result = event.payload as ToolResultPayload
307
363
  if (
308
364
  result.preliminary === true ||
309
- state.response?.generation?.generationId !== result.generationId
365
+ state.response?.generation?.requestId !== result.requestId ||
366
+ state.response.generation.generationId !== result.generationId
310
367
  ) {
311
368
  return state
312
369
  }
@@ -314,7 +371,7 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
314
371
  ...response,
315
372
  calls: response.calls.map((candidate) =>
316
373
  candidate.call.toolCallId === result.toolCallId
317
- ? { ...candidate, terminal: true }
374
+ ? completeCall(candidate)
318
375
  : candidate,
319
376
  ),
320
377
  }))
@@ -323,7 +380,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
323
380
  const input = event.payload as InputRequestedPayload
324
381
  if (
325
382
  state.response?.responseMessageId !== input.messageId ||
326
- state.response.generation?.generationId !== input.generationId
383
+ state.response.generation?.requestId !== input.requestId ||
384
+ state.response.generation.generationId !== input.generationId
327
385
  ) {
328
386
  return state
329
387
  }
@@ -341,12 +399,7 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
341
399
  }))
342
400
  }
343
401
  case 'ai.input.responded': {
344
- const input = event.payload as {
345
- messageId: string
346
- generationId: string
347
- inputId: string
348
- name: string
349
- }
402
+ const input = event.payload as InputRespondedPayload
350
403
  const requested = state.response?.inputs.find(
351
404
  (candidate) =>
352
405
  candidate.messageId === input.messageId &&
@@ -356,7 +409,8 @@ export const foldCoordinator = <M extends UIMessage, D extends EventDefs>(
356
409
  )
357
410
  if (
358
411
  state.response?.responseMessageId !== input.messageId ||
359
- state.response.generation?.generationId !== input.generationId ||
412
+ state.response.generation?.requestId !== input.requestId ||
413
+ state.response.generation.generationId !== input.generationId ||
360
414
  requested === undefined
361
415
  ) {
362
416
  return state
@@ -386,7 +440,7 @@ export const aiCoordinatorReducer = <
386
440
  ): Reducer<D, AICoordinatorState> =>
387
441
  contract
388
442
  .reducer({
389
- name: 'a2.ai.coordinator.v5',
443
+ name: 'a2.ai.coordinator.v6',
390
444
  initialState: {
391
445
  closed: false,
392
446
  queued: [],
package/src/ai-id.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { idempotentId } from './idempotent-id.ts'
2
+
3
+ export const AI_EVENT_ID_PREFIX = 'a2.ai:'
4
+
5
+ export const aiEventId = async (...parts: string[]): Promise<string> =>
6
+ `${AI_EVENT_ID_PREFIX}${await idempotentId(...parts)}`
@@ -0,0 +1,75 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { asSchema } from 'ai'
3
+ import type { Instructions, ModelMessage, ToolSet } from 'ai'
4
+ import type { AICoordinatorState } from './ai-coordinator.ts'
5
+
6
+ const withoutProviderOptions = <T extends object>(value: T): T => {
7
+ const result = { ...value }
8
+ Reflect.deleteProperty(result, 'providerOptions')
9
+ return result
10
+ }
11
+
12
+ const estimatedMessage = (message: ModelMessage): unknown => ({
13
+ ...withoutProviderOptions(message),
14
+ content:
15
+ typeof message.content === 'string'
16
+ ? message.content
17
+ : message.content.map((part) =>
18
+ part.type === 'tool-result'
19
+ ? {
20
+ ...withoutProviderOptions(part),
21
+ output: {
22
+ ...withoutProviderOptions(part.output),
23
+ ...(part.output.type === 'content'
24
+ ? {
25
+ value: part.output.value.map(withoutProviderOptions),
26
+ }
27
+ : {}),
28
+ },
29
+ }
30
+ : withoutProviderOptions(part),
31
+ ),
32
+ })
33
+
34
+ export const estimateInputTokens = async (options: {
35
+ messages: ModelMessage[]
36
+ instructions: Instructions | undefined
37
+ tools: ToolSet
38
+ }): Promise<number> => {
39
+ const tools = await Promise.all(
40
+ Object.entries(options.tools).map(async ([name, tool]) => ({
41
+ name,
42
+ description: tool.description,
43
+ inputSchema: await asSchema(tool.inputSchema).jsonSchema,
44
+ })),
45
+ )
46
+ const instructions = options.instructions
47
+ return Math.ceil(
48
+ Buffer.byteLength(
49
+ JSON.stringify({
50
+ messages: options.messages.map(estimatedMessage),
51
+ instructions:
52
+ instructions === undefined || typeof instructions === 'string'
53
+ ? instructions
54
+ : Array.isArray(instructions)
55
+ ? instructions.map(estimatedMessage)
56
+ : estimatedMessage(instructions),
57
+ tools,
58
+ }),
59
+ 'utf8',
60
+ ) / 4,
61
+ )
62
+ }
63
+
64
+ export const measuredInputTokens = (options: {
65
+ estimate: number
66
+ model: string
67
+ calibration: AICoordinatorState['calibration']
68
+ }): number => {
69
+ const previous = options.calibration
70
+ return previous !== undefined &&
71
+ previous.model === options.model &&
72
+ options.estimate >= previous.estimate
73
+ ? Math.ceil(previous.inputTokens + options.estimate - previous.estimate)
74
+ : options.estimate
75
+ }