@abloatai/ablo 0.3.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 (278) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +230 -0
  5. package/dist/BaseSyncedStore.d.ts +709 -0
  6. package/dist/BaseSyncedStore.js +1843 -0
  7. package/dist/Database.d.ts +344 -0
  8. package/dist/Database.js +1259 -0
  9. package/dist/LazyReferenceCollection.d.ts +181 -0
  10. package/dist/LazyReferenceCollection.js +460 -0
  11. package/dist/Model.d.ts +339 -0
  12. package/dist/Model.js +715 -0
  13. package/dist/ModelRegistry.d.ts +200 -0
  14. package/dist/ModelRegistry.js +535 -0
  15. package/dist/NetworkMonitor.d.ts +27 -0
  16. package/dist/NetworkMonitor.js +73 -0
  17. package/dist/ObjectPool.d.ts +202 -0
  18. package/dist/ObjectPool.js +1106 -0
  19. package/dist/SyncClient.d.ts +489 -0
  20. package/dist/SyncClient.js +1555 -0
  21. package/dist/SyncEngineContext.d.ts +46 -0
  22. package/dist/SyncEngineContext.js +74 -0
  23. package/dist/adapters/alwaysOnline.d.ts +16 -0
  24. package/dist/adapters/alwaysOnline.js +19 -0
  25. package/dist/adapters/inMemoryStorage.d.ts +30 -0
  26. package/dist/adapters/inMemoryStorage.js +94 -0
  27. package/dist/agent/Agent.d.ts +358 -0
  28. package/dist/agent/Agent.js +500 -0
  29. package/dist/agent/index.d.ts +115 -0
  30. package/dist/agent/index.js +128 -0
  31. package/dist/agent/session.d.ts +90 -0
  32. package/dist/agent/session.js +156 -0
  33. package/dist/agent/types.d.ts +73 -0
  34. package/dist/agent/types.js +10 -0
  35. package/dist/ai-sdk/coordination-context.d.ts +51 -0
  36. package/dist/ai-sdk/coordination-context.js +107 -0
  37. package/dist/ai-sdk/index.d.ts +68 -0
  38. package/dist/ai-sdk/index.js +68 -0
  39. package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
  40. package/dist/ai-sdk/intent-broadcast.js +72 -0
  41. package/dist/ai-sdk/wrap.d.ts +67 -0
  42. package/dist/ai-sdk/wrap.js +45 -0
  43. package/dist/api/index.d.ts +10 -0
  44. package/dist/api/index.js +9 -0
  45. package/dist/auth/index.d.ts +137 -0
  46. package/dist/auth/index.js +246 -0
  47. package/dist/client/Ablo.d.ts +835 -0
  48. package/dist/client/Ablo.js +1440 -0
  49. package/dist/client/ApiClient.d.ts +200 -0
  50. package/dist/client/ApiClient.js +659 -0
  51. package/dist/client/auth.d.ts +79 -0
  52. package/dist/client/auth.js +81 -0
  53. package/dist/client/createInternalComponents.d.ts +44 -0
  54. package/dist/client/createInternalComponents.js +88 -0
  55. package/dist/client/createModelProxy.d.ts +152 -0
  56. package/dist/client/createModelProxy.js +199 -0
  57. package/dist/client/identity.d.ts +63 -0
  58. package/dist/client/identity.js +156 -0
  59. package/dist/client/index.d.ts +36 -0
  60. package/dist/client/index.js +33 -0
  61. package/dist/client/persistence.d.ts +7 -0
  62. package/dist/client/persistence.js +11 -0
  63. package/dist/client/validateAbloOptions.d.ts +42 -0
  64. package/dist/client/validateAbloOptions.js +43 -0
  65. package/dist/config/index.d.ts +10 -0
  66. package/dist/config/index.js +12 -0
  67. package/dist/context.d.ts +27 -0
  68. package/dist/context.js +58 -0
  69. package/dist/core/DatabaseManager.d.ts +108 -0
  70. package/dist/core/DatabaseManager.js +361 -0
  71. package/dist/core/QueryProcessor.d.ts +77 -0
  72. package/dist/core/QueryProcessor.js +262 -0
  73. package/dist/core/QueryView.d.ts +64 -0
  74. package/dist/core/QueryView.js +219 -0
  75. package/dist/core/StoreManager.d.ts +131 -0
  76. package/dist/core/StoreManager.js +334 -0
  77. package/dist/core/ViewRegistry.d.ts +20 -0
  78. package/dist/core/ViewRegistry.js +55 -0
  79. package/dist/core/index.d.ts +34 -0
  80. package/dist/core/index.js +59 -0
  81. package/dist/core/openIDBWithTimeout.d.ts +27 -0
  82. package/dist/core/openIDBWithTimeout.js +63 -0
  83. package/dist/core/query-utils.d.ts +37 -0
  84. package/dist/core/query-utils.js +60 -0
  85. package/dist/errors.d.ts +235 -0
  86. package/dist/errors.js +243 -0
  87. package/dist/index.d.ts +41 -0
  88. package/dist/index.js +82 -0
  89. package/dist/interfaces/headless.d.ts +95 -0
  90. package/dist/interfaces/headless.js +41 -0
  91. package/dist/interfaces/index.d.ts +321 -0
  92. package/dist/interfaces/index.js +8 -0
  93. package/dist/mutators/RecordingTransaction.d.ts +36 -0
  94. package/dist/mutators/RecordingTransaction.js +216 -0
  95. package/dist/mutators/Transaction.d.ts +48 -0
  96. package/dist/mutators/Transaction.js +64 -0
  97. package/dist/mutators/UndoManager.d.ts +114 -0
  98. package/dist/mutators/UndoManager.js +143 -0
  99. package/dist/mutators/defineMutators.d.ts +55 -0
  100. package/dist/mutators/defineMutators.js +28 -0
  101. package/dist/policy/index.d.ts +19 -0
  102. package/dist/policy/index.js +18 -0
  103. package/dist/policy/types.d.ts +74 -0
  104. package/dist/policy/types.js +17 -0
  105. package/dist/principal.d.ts +44 -0
  106. package/dist/principal.js +49 -0
  107. package/dist/query/client.d.ts +43 -0
  108. package/dist/query/client.js +84 -0
  109. package/dist/query/index.d.ts +6 -0
  110. package/dist/query/index.js +5 -0
  111. package/dist/query/types.d.ts +143 -0
  112. package/dist/query/types.js +36 -0
  113. package/dist/react/AbloProvider.d.ts +205 -0
  114. package/dist/react/AbloProvider.js +398 -0
  115. package/dist/react/ClientSideSuspense.d.ts +36 -0
  116. package/dist/react/ClientSideSuspense.js +17 -0
  117. package/dist/react/DefaultFallback.d.ts +24 -0
  118. package/dist/react/DefaultFallback.js +43 -0
  119. package/dist/react/SyncGroupProvider.d.ts +19 -0
  120. package/dist/react/SyncGroupProvider.js +44 -0
  121. package/dist/react/context.d.ts +161 -0
  122. package/dist/react/context.js +35 -0
  123. package/dist/react/index.d.ts +64 -0
  124. package/dist/react/index.js +73 -0
  125. package/dist/react/internalContext.d.ts +35 -0
  126. package/dist/react/internalContext.js +3 -0
  127. package/dist/react/useAblo.d.ts +72 -0
  128. package/dist/react/useAblo.js +63 -0
  129. package/dist/react/useCurrentUserId.d.ts +21 -0
  130. package/dist/react/useCurrentUserId.js +33 -0
  131. package/dist/react/useErrorListener.d.ts +20 -0
  132. package/dist/react/useErrorListener.js +39 -0
  133. package/dist/react/useIntent.d.ts +29 -0
  134. package/dist/react/useIntent.js +42 -0
  135. package/dist/react/useMutate.d.ts +83 -0
  136. package/dist/react/useMutate.js +122 -0
  137. package/dist/react/useMutationFailureListener.d.ts +26 -0
  138. package/dist/react/useMutationFailureListener.js +38 -0
  139. package/dist/react/useMutators.d.ts +56 -0
  140. package/dist/react/useMutators.js +66 -0
  141. package/dist/react/usePresence.d.ts +32 -0
  142. package/dist/react/usePresence.js +41 -0
  143. package/dist/react/useQuery.d.ts +123 -0
  144. package/dist/react/useQuery.js +145 -0
  145. package/dist/react/useReactive.d.ts +35 -0
  146. package/dist/react/useReactive.js +111 -0
  147. package/dist/react/useReader.d.ts +69 -0
  148. package/dist/react/useReader.js +73 -0
  149. package/dist/react/useSyncStatus.d.ts +61 -0
  150. package/dist/react/useSyncStatus.js +76 -0
  151. package/dist/react/useUndoScope.d.ts +36 -0
  152. package/dist/react/useUndoScope.js +73 -0
  153. package/dist/realtime/index.d.ts +10 -0
  154. package/dist/realtime/index.js +9 -0
  155. package/dist/schema/field.d.ts +134 -0
  156. package/dist/schema/field.js +264 -0
  157. package/dist/schema/index.d.ts +29 -0
  158. package/dist/schema/index.js +38 -0
  159. package/dist/schema/model.d.ts +326 -0
  160. package/dist/schema/model.js +89 -0
  161. package/dist/schema/queries.d.ts +203 -0
  162. package/dist/schema/queries.js +145 -0
  163. package/dist/schema/relation.d.ts +172 -0
  164. package/dist/schema/relation.js +104 -0
  165. package/dist/schema/schema.d.ts +259 -0
  166. package/dist/schema/schema.js +188 -0
  167. package/dist/schema/sugar.d.ts +129 -0
  168. package/dist/schema/sugar.js +94 -0
  169. package/dist/source/index.d.ts +423 -0
  170. package/dist/source/index.js +320 -0
  171. package/dist/source/pushQueue.d.ts +112 -0
  172. package/dist/source/pushQueue.js +249 -0
  173. package/dist/stores/ObjectStore.d.ts +103 -0
  174. package/dist/stores/ObjectStore.js +371 -0
  175. package/dist/stores/ObjectStoreContract.d.ts +39 -0
  176. package/dist/stores/ObjectStoreContract.js +1 -0
  177. package/dist/stores/SyncActionStore.d.ts +101 -0
  178. package/dist/stores/SyncActionStore.js +481 -0
  179. package/dist/sync/BootstrapHelper.d.ts +127 -0
  180. package/dist/sync/BootstrapHelper.js +434 -0
  181. package/dist/sync/ConnectionManager.d.ts +136 -0
  182. package/dist/sync/ConnectionManager.js +465 -0
  183. package/dist/sync/HydrationCoordinator.d.ts +137 -0
  184. package/dist/sync/HydrationCoordinator.js +468 -0
  185. package/dist/sync/NetworkProbe.d.ts +43 -0
  186. package/dist/sync/NetworkProbe.js +113 -0
  187. package/dist/sync/OfflineFlush.d.ts +9 -0
  188. package/dist/sync/OfflineFlush.js +22 -0
  189. package/dist/sync/OfflineTransactionStore.d.ts +37 -0
  190. package/dist/sync/OfflineTransactionStore.js +263 -0
  191. package/dist/sync/SyncWebSocket.d.ts +663 -0
  192. package/dist/sync/SyncWebSocket.js +1336 -0
  193. package/dist/sync/createIntentStream.d.ts +33 -0
  194. package/dist/sync/createIntentStream.js +243 -0
  195. package/dist/sync/createPresenceStream.d.ts +46 -0
  196. package/dist/sync/createPresenceStream.js +192 -0
  197. package/dist/sync/createSnapshot.d.ts +33 -0
  198. package/dist/sync/createSnapshot.js +124 -0
  199. package/dist/sync/participants.d.ts +114 -0
  200. package/dist/sync/participants.js +336 -0
  201. package/dist/sync/schemas.d.ts +79 -0
  202. package/dist/sync/schemas.js +78 -0
  203. package/dist/testing/fixtures/bootstrap.d.ts +45 -0
  204. package/dist/testing/fixtures/bootstrap.js +53 -0
  205. package/dist/testing/fixtures/deltas.d.ts +86 -0
  206. package/dist/testing/fixtures/deltas.js +139 -0
  207. package/dist/testing/fixtures/models.d.ts +82 -0
  208. package/dist/testing/fixtures/models.js +270 -0
  209. package/dist/testing/helpers/react-wrapper.d.ts +66 -0
  210. package/dist/testing/helpers/react-wrapper.js +64 -0
  211. package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
  212. package/dist/testing/helpers/sync-engine-harness.js +70 -0
  213. package/dist/testing/helpers/wait.d.ts +25 -0
  214. package/dist/testing/helpers/wait.js +44 -0
  215. package/dist/testing/index.d.ts +21 -0
  216. package/dist/testing/index.js +32 -0
  217. package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
  218. package/dist/testing/mocks/MockMutationExecutor.js +139 -0
  219. package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
  220. package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
  221. package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
  222. package/dist/testing/mocks/MockSyncContext.js +100 -0
  223. package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
  224. package/dist/testing/mocks/MockSyncStore.js +171 -0
  225. package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
  226. package/dist/testing/mocks/MockWebSocket.js +117 -0
  227. package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
  228. package/dist/transactions/OptimisticEchoTracker.js +104 -0
  229. package/dist/transactions/TransactionQueue.d.ts +499 -0
  230. package/dist/transactions/TransactionQueue.js +1895 -0
  231. package/dist/transactions/index.d.ts +16 -0
  232. package/dist/transactions/index.js +7 -0
  233. package/dist/transactions/mutation-error-handler.d.ts +5 -0
  234. package/dist/transactions/mutation-error-handler.js +39 -0
  235. package/dist/types/global.d.ts +107 -0
  236. package/dist/types/global.js +38 -0
  237. package/dist/types/index.d.ts +241 -0
  238. package/dist/types/index.js +70 -0
  239. package/dist/types/streams.d.ts +495 -0
  240. package/dist/types/streams.js +11 -0
  241. package/dist/utils/asyncIterator.d.ts +41 -0
  242. package/dist/utils/asyncIterator.js +142 -0
  243. package/dist/utils/duration.d.ts +28 -0
  244. package/dist/utils/duration.js +47 -0
  245. package/dist/utils/mobx-setup.d.ts +42 -0
  246. package/dist/utils/mobx-setup.js +381 -0
  247. package/docs/api-keys.md +24 -0
  248. package/docs/api.md +230 -0
  249. package/docs/audit.md +81 -0
  250. package/docs/capabilities.md +163 -0
  251. package/docs/client-behavior.md +202 -0
  252. package/docs/data-sources.md +214 -0
  253. package/docs/examples/agent-human.md +84 -0
  254. package/docs/examples/ai-sdk-tool.md +92 -0
  255. package/docs/examples/existing-python-backend.md +249 -0
  256. package/docs/examples/nextjs.md +88 -0
  257. package/docs/examples/server-agent.md +86 -0
  258. package/docs/guarantees.md +148 -0
  259. package/docs/index.md +97 -0
  260. package/docs/integration-guide.md +493 -0
  261. package/docs/interaction-model.md +140 -0
  262. package/docs/mcp/claude-code.md +43 -0
  263. package/docs/mcp/cursor.md +53 -0
  264. package/docs/mcp/windsurf.md +46 -0
  265. package/docs/mcp.md +59 -0
  266. package/docs/quickstart.md +152 -0
  267. package/docs/react.md +115 -0
  268. package/docs/roadmap.md +45 -0
  269. package/examples/README.md +54 -0
  270. package/examples/data-source/README.md +102 -0
  271. package/examples/data-source/ablo-driver.ts +89 -0
  272. package/examples/data-source/customer-server.ts +208 -0
  273. package/examples/data-source/run.ts +101 -0
  274. package/examples/data-source/schema.ts +25 -0
  275. package/examples/quickstart.ts +54 -0
  276. package/examples/tsconfig.json +16 -0
  277. package/llms.txt +143 -0
  278. package/package.json +147 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
3
+ * middleware.
4
+ *
5
+ * Two cross-cutting middlewares for any AI SDK consumer using
6
+ * `streamText` / `generateText`:
7
+ *
8
+ * - `intentBroadcastMiddleware` — agent declares what it's about
9
+ * to mutate via `intent_begin`, abandons the claim at stream end.
10
+ * Peers see the broadcast in their presence stream's
11
+ * `activeIntents` field and can defer / yield / surface
12
+ * "agent is editing this entity right now."
13
+ *
14
+ * - `coordinationContextMiddleware` — reads peer intents from local
15
+ * presence cache before the LLM call, injects a
16
+ * `<multiplayer_context>` system note when peers are editing
17
+ * the same entity. The AI gets coordination awareness without
18
+ * extra round-trips.
19
+ *
20
+ * Compose them with the AI SDK's `wrapLanguageModel`:
21
+ *
22
+ * ```ts
23
+ * import { wrapLanguageModel, streamText } from 'ai';
24
+ * import {
25
+ * intentBroadcastMiddleware,
26
+ * coordinationContextMiddleware,
27
+ * } from '@ablo/sync-engine-internal/ai-sdk';
28
+ *
29
+ * const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
30
+ *
31
+ * const wrappedModel = wrapLanguageModel({
32
+ * model: anthropic('claude-opus-4-7'),
33
+ * middleware: [
34
+ * coordinationContextMiddleware({ agent, target }),
35
+ * intentBroadcastMiddleware({ agent, target }),
36
+ * ],
37
+ * });
38
+ *
39
+ * // Consumer keeps full control over messages, tools, system prompt:
40
+ * const result = streamText({
41
+ * model: wrappedModel,
42
+ * messages: [...],
43
+ * tools: { ... },
44
+ * system: '...',
45
+ * });
46
+ * ```
47
+ *
48
+ * Or use the convenience composition for the common case:
49
+ *
50
+ * ```ts
51
+ * import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
52
+ *
53
+ * const wrappedModel = wrapWithMultiplayer({
54
+ * model: anthropic('claude-opus-4-7'),
55
+ * agent,
56
+ * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
57
+ * });
58
+ * ```
59
+ *
60
+ * Order matters: `coordinationContextMiddleware`'s `transformParams`
61
+ * runs at param-transform time (before the model call), reading peer
62
+ * intents *before* this agent's broadcast lands in its own cache.
63
+ * `intentBroadcastMiddleware`'s `wrapStream` runs around the actual
64
+ * call. Self-claim doesn't pollute the peer-intent read.
65
+ */
66
+ export { intentBroadcastMiddleware, type IntentTarget, type IntentBroadcastMiddlewareOptions, } from './intent-broadcast.js';
67
+ export { coordinationContextMiddleware, type CoordinationContextMiddlewareOptions, } from './coordination-context.js';
68
+ export { wrapWithMultiplayer, type WrapWithMultiplayerOptions } from './wrap.js';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
3
+ * middleware.
4
+ *
5
+ * Two cross-cutting middlewares for any AI SDK consumer using
6
+ * `streamText` / `generateText`:
7
+ *
8
+ * - `intentBroadcastMiddleware` — agent declares what it's about
9
+ * to mutate via `intent_begin`, abandons the claim at stream end.
10
+ * Peers see the broadcast in their presence stream's
11
+ * `activeIntents` field and can defer / yield / surface
12
+ * "agent is editing this entity right now."
13
+ *
14
+ * - `coordinationContextMiddleware` — reads peer intents from local
15
+ * presence cache before the LLM call, injects a
16
+ * `<multiplayer_context>` system note when peers are editing
17
+ * the same entity. The AI gets coordination awareness without
18
+ * extra round-trips.
19
+ *
20
+ * Compose them with the AI SDK's `wrapLanguageModel`:
21
+ *
22
+ * ```ts
23
+ * import { wrapLanguageModel, streamText } from 'ai';
24
+ * import {
25
+ * intentBroadcastMiddleware,
26
+ * coordinationContextMiddleware,
27
+ * } from '@ablo/sync-engine-internal/ai-sdk';
28
+ *
29
+ * const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
30
+ *
31
+ * const wrappedModel = wrapLanguageModel({
32
+ * model: anthropic('claude-opus-4-7'),
33
+ * middleware: [
34
+ * coordinationContextMiddleware({ agent, target }),
35
+ * intentBroadcastMiddleware({ agent, target }),
36
+ * ],
37
+ * });
38
+ *
39
+ * // Consumer keeps full control over messages, tools, system prompt:
40
+ * const result = streamText({
41
+ * model: wrappedModel,
42
+ * messages: [...],
43
+ * tools: { ... },
44
+ * system: '...',
45
+ * });
46
+ * ```
47
+ *
48
+ * Or use the convenience composition for the common case:
49
+ *
50
+ * ```ts
51
+ * import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
52
+ *
53
+ * const wrappedModel = wrapWithMultiplayer({
54
+ * model: anthropic('claude-opus-4-7'),
55
+ * agent,
56
+ * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
57
+ * });
58
+ * ```
59
+ *
60
+ * Order matters: `coordinationContextMiddleware`'s `transformParams`
61
+ * runs at param-transform time (before the model call), reading peer
62
+ * intents *before* this agent's broadcast lands in its own cache.
63
+ * `intentBroadcastMiddleware`'s `wrapStream` runs around the actual
64
+ * call. Self-claim doesn't pollute the peer-intent read.
65
+ */
66
+ export { intentBroadcastMiddleware, } from './intent-broadcast.js';
67
+ export { coordinationContextMiddleware, } from './coordination-context.js';
68
+ export { wrapWithMultiplayer } from './wrap.js';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Intent broadcast middleware — wraps a language model so the agent
3
+ * declares "I'm about to edit entity X" over the sync engine's
4
+ * intent primitive at stream start, and abandons the claim at
5
+ * stream end.
6
+ *
7
+ * Cross-cutting by design — composes via the AI SDK's
8
+ * `wrapLanguageModel`. Same middleware works for every chat
9
+ * surface, and for non-chat agent loops that share the AI SDK's
10
+ * middleware interface (workers, MCP tools, autonomous loops).
11
+ *
12
+ * Open-source-clean: depends only on `@ai-sdk/provider` types and
13
+ * the package's own `SyncAgent`. No app-specific assumptions —
14
+ * Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
15
+ *
16
+ * Cost: one WS frame at stream start (`intent_begin`), one at end
17
+ * (`intent_abandon`). No DB I/O, no extra LLM tokens.
18
+ */
19
+ import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
20
+ import type { Ablo } from '../client/Ablo.js';
21
+ import type { SchemaRecord } from '../schema/schema.js';
22
+ /**
23
+ * Target entity for the intent broadcast.
24
+ *
25
+ * `entityType` is a free-form string — convention is the schema's
26
+ * typename (e.g. `'SlideDeck'`, `'Task'`, `'Matter'`) so peers can
27
+ * filter consistently. The wire format treats it opaquely.
28
+ */
29
+ export interface IntentTarget {
30
+ readonly entityType: string;
31
+ readonly entityId: string;
32
+ /** Optional path for file/document-like targets. */
33
+ readonly path?: string;
34
+ /** Optional line/column range for partial-entity coordination. */
35
+ readonly range?: {
36
+ readonly startLine: number;
37
+ readonly endLine: number;
38
+ readonly startColumn?: number;
39
+ readonly endColumn?: number;
40
+ };
41
+ /**
42
+ * Optional sub-field within the entity. Useful when the agent
43
+ * knows it's only editing a specific field — peers can filter
44
+ * on the field too.
45
+ */
46
+ readonly field?: string;
47
+ /** App-defined structured metadata. Opaque to the core SDK. */
48
+ readonly meta?: Record<string, unknown>;
49
+ /**
50
+ * Hint for the server-side TTL on the claim. Caps at 10 minutes
51
+ * server-side; default 60s — typical chat turn.
52
+ */
53
+ readonly estimatedMs?: number;
54
+ }
55
+ export interface IntentBroadcastMiddlewareOptions<R extends SchemaRecord = SchemaRecord> {
56
+ /** Connected Ablo. Null disables the middleware (no-op). */
57
+ readonly agent: Ablo<R> | null;
58
+ /** Target entity. Null skips the broadcast (purely conversational). */
59
+ readonly target: IntentTarget | null;
60
+ /**
61
+ * Action verb describing what the agent is doing. Convention:
62
+ * `'edit'`, `'read'`, `'review'`, `'generate'`. Default `'edit'`.
63
+ */
64
+ readonly action?: string;
65
+ }
66
+ /**
67
+ * Build the middleware. When `agent` or `target` is null, returns a
68
+ * pass-through — keeps call sites unconditional regardless of
69
+ * whether the surface has an entity in scope.
70
+ *
71
+ * Generic over the schema record so callers passing
72
+ * `Ablo<typeof schema>` don't have to widen — `Ablo<S>` and
73
+ * `Ablo<SchemaRecord>` are structurally non-compatible because the
74
+ * widened version collapses model proxies to an index signature
75
+ * that clashes with the named methods (`ready`, `dispose`, etc.).
76
+ */
77
+ export declare function intentBroadcastMiddleware<R extends SchemaRecord = SchemaRecord>(options: IntentBroadcastMiddlewareOptions<R>): LanguageModelV3Middleware;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Intent broadcast middleware — wraps a language model so the agent
3
+ * declares "I'm about to edit entity X" over the sync engine's
4
+ * intent primitive at stream start, and abandons the claim at
5
+ * stream end.
6
+ *
7
+ * Cross-cutting by design — composes via the AI SDK's
8
+ * `wrapLanguageModel`. Same middleware works for every chat
9
+ * surface, and for non-chat agent loops that share the AI SDK's
10
+ * middleware interface (workers, MCP tools, autonomous loops).
11
+ *
12
+ * Open-source-clean: depends only on `@ai-sdk/provider` types and
13
+ * the package's own `SyncAgent`. No app-specific assumptions —
14
+ * Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
15
+ *
16
+ * Cost: one WS frame at stream start (`intent_begin`), one at end
17
+ * (`intent_abandon`). No DB I/O, no extra LLM tokens.
18
+ */
19
+ /**
20
+ * Build the middleware. When `agent` or `target` is null, returns a
21
+ * pass-through — keeps call sites unconditional regardless of
22
+ * whether the surface has an entity in scope.
23
+ *
24
+ * Generic over the schema record so callers passing
25
+ * `Ablo<typeof schema>` don't have to widen — `Ablo<S>` and
26
+ * `Ablo<SchemaRecord>` are structurally non-compatible because the
27
+ * widened version collapses model proxies to an index signature
28
+ * that clashes with the named methods (`ready`, `dispose`, etc.).
29
+ */
30
+ export function intentBroadcastMiddleware(options) {
31
+ const { agent, target } = options;
32
+ const action = options.action ?? 'edit';
33
+ const openClaim = () => agent && target
34
+ ? agent.intents.claim({
35
+ type: target.entityType,
36
+ id: target.entityId,
37
+ path: target.path,
38
+ range: target.range,
39
+ field: target.field,
40
+ meta: target.meta,
41
+ }, { reason: action, ttl: target.estimatedMs ?? 60_000 })
42
+ : null;
43
+ return {
44
+ specificationVersion: 'v3',
45
+ // The AI SDK's middleware contract passes a no-arg `doStream` /
46
+ // `doGenerate` thunk — params have already been transformed by
47
+ // any earlier `transformParams` middleware in the chain. We
48
+ // open the claim, call the inner, abandon when the inner
49
+ // resolves (or rejects).
50
+ async wrapStream({ doStream }) {
51
+ const handle = openClaim();
52
+ try {
53
+ return await doStream();
54
+ }
55
+ finally {
56
+ // Always abandon — even on error. The server's TTL would
57
+ // eventually clean up regardless, but explicit release means
58
+ // peers see the claim drop the moment generation completes.
59
+ handle?.revoke();
60
+ }
61
+ },
62
+ async wrapGenerate({ doGenerate }) {
63
+ const handle = openClaim();
64
+ try {
65
+ return await doGenerate();
66
+ }
67
+ finally {
68
+ handle?.revoke();
69
+ }
70
+ },
71
+ };
72
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Convenience composition for the common case — wraps a language
3
+ * model with both multiplayer middlewares (intent broadcast +
4
+ * coordination context) in the right order.
5
+ *
6
+ * Consumers who want full control over middleware composition (add
7
+ * caching / observability / their own custom middleware) should use
8
+ * the factories directly: `intentBroadcastMiddleware`,
9
+ * `coordinationContextMiddleware`. This helper is the one-liner for
10
+ * the 90% case.
11
+ *
12
+ * Stays explicit about its scope — wraps the MODEL only. Consumer
13
+ * keeps full control over their `streamText` / `generateText` call
14
+ * (messages, tools, system prompt, provider options, onFinish, etc.).
15
+ *
16
+ * ```ts
17
+ * const wrapped = wrapWithMultiplayer({
18
+ * model: anthropic('claude-opus-4-7'),
19
+ * agent,
20
+ * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
21
+ * });
22
+ *
23
+ * const result = streamText({
24
+ * model: wrapped,
25
+ * messages: myMessages,
26
+ * tools: myTools,
27
+ * system: mySystem,
28
+ * // ... anything else the consumer's app needs
29
+ * });
30
+ * ```
31
+ */
32
+ import { wrapLanguageModel } from 'ai';
33
+ import type { LanguageModelV3, LanguageModelV3Middleware } from '@ai-sdk/provider';
34
+ import type { Ablo } from '../client/Ablo.js';
35
+ import type { SchemaRecord } from '../schema/schema.js';
36
+ import { type IntentTarget } from './intent-broadcast.js';
37
+ export interface WrapWithMultiplayerOptions {
38
+ /** The base language model to wrap. Consumer brings their own. */
39
+ readonly model: LanguageModelV3;
40
+ /** Connected SyncAgent. Null = pass-through wrap (no broadcast, no read). */
41
+ readonly agent: Ablo<SchemaRecord> | null;
42
+ /** Target entity. Null = pass-through wrap. */
43
+ readonly target: IntentTarget | null;
44
+ /**
45
+ * Optional action verb for the broadcast. Default `'edit'`.
46
+ * Convention: `'edit'`, `'read'`, `'review'`, `'generate'`.
47
+ */
48
+ readonly action?: string;
49
+ /**
50
+ * Optional intentIds to exclude from the coordination-context
51
+ * read — typically the caller's own claim if they're composing
52
+ * multiple wrappings. Most consumers leave this empty.
53
+ */
54
+ readonly excludeIntentIds?: readonly string[];
55
+ /**
56
+ * Optional extra middleware to compose. Runs in the order given,
57
+ * INSIDE the multiplayer middlewares (so the multiplayer wrap is
58
+ * the outer-most). Useful for caching, observability, custom
59
+ * transforms that should not affect the multiplayer signal.
60
+ *
61
+ * For full control over ordering, skip this helper and call
62
+ * `wrapLanguageModel` directly with all middleware in the order
63
+ * you want.
64
+ */
65
+ readonly extraMiddleware?: readonly LanguageModelV3Middleware[];
66
+ }
67
+ export declare function wrapWithMultiplayer(options: WrapWithMultiplayerOptions): ReturnType<typeof wrapLanguageModel>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Convenience composition for the common case — wraps a language
3
+ * model with both multiplayer middlewares (intent broadcast +
4
+ * coordination context) in the right order.
5
+ *
6
+ * Consumers who want full control over middleware composition (add
7
+ * caching / observability / their own custom middleware) should use
8
+ * the factories directly: `intentBroadcastMiddleware`,
9
+ * `coordinationContextMiddleware`. This helper is the one-liner for
10
+ * the 90% case.
11
+ *
12
+ * Stays explicit about its scope — wraps the MODEL only. Consumer
13
+ * keeps full control over their `streamText` / `generateText` call
14
+ * (messages, tools, system prompt, provider options, onFinish, etc.).
15
+ *
16
+ * ```ts
17
+ * const wrapped = wrapWithMultiplayer({
18
+ * model: anthropic('claude-opus-4-7'),
19
+ * agent,
20
+ * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
21
+ * });
22
+ *
23
+ * const result = streamText({
24
+ * model: wrapped,
25
+ * messages: myMessages,
26
+ * tools: myTools,
27
+ * system: mySystem,
28
+ * // ... anything else the consumer's app needs
29
+ * });
30
+ * ```
31
+ */
32
+ import { wrapLanguageModel } from 'ai';
33
+ import { intentBroadcastMiddleware, } from './intent-broadcast.js';
34
+ import { coordinationContextMiddleware } from './coordination-context.js';
35
+ export function wrapWithMultiplayer(options) {
36
+ const { model, agent, target, action, excludeIntentIds, extraMiddleware } = options;
37
+ return wrapLanguageModel({
38
+ model,
39
+ middleware: [
40
+ coordinationContextMiddleware({ agent, target, excludeIntentIds }),
41
+ intentBroadcastMiddleware({ agent, target, action }),
42
+ ...(extraMiddleware ?? []),
43
+ ],
44
+ });
45
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal compatibility entrypoint for the stateless hosted protocol client.
3
+ *
4
+ * Use this build for serverless functions, scripts, and backends that want
5
+ * Resource / Intent / Commit over HTTP without the realtime sync runtime.
6
+ */
7
+ export { createProtocolClient, createProtocolClient as Ablo, type AbloApi, type AbloApiClientOptions, type AbloApiIntents, type Agent, type AgentIntentInput, type AgentIntentOptions, type AgentOptions, type AgentResourceClient, type AgentResourceReadOptions, type AgentResourceMutationOptions, type AgentRunContext, type AgentRunDone, type AgentRunFailed, type AgentRunCancelled, type AgentRunOptions, type AgentRunResult, type AgentRunStatus, type Capability, type CapabilityCreateOptions, type CapabilityParticipantKind, type CapabilityRecord, type CapabilityResource, type CapabilityRevocation, type CapabilityScope, type Task, type TaskCloseOptions, type TaskCloseResult, type TaskCreateOptions, type TaskResource, } from '../client/ApiClient.js';
8
+ export type { CommitCreateOptions, CommitOperationInput, CommitReceipt, CommitWait, IntentCreateOptions, IntentHandle, IntentWaitOptions, BusyOptions, BusyPolicy, ResourceClient, ResourceIntent, ResourceMutationOptions, ResourceReadOptions, ResourceRead, ResourceTarget, } from '../client/Ablo.js';
9
+ import { createProtocolClient } from '../client/ApiClient.js';
10
+ export default createProtocolClient;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Internal compatibility entrypoint for the stateless hosted protocol client.
3
+ *
4
+ * Use this build for serverless functions, scripts, and backends that want
5
+ * Resource / Intent / Commit over HTTP without the realtime sync runtime.
6
+ */
7
+ export { createProtocolClient, createProtocolClient as Ablo, } from '../client/ApiClient.js';
8
+ import { createProtocolClient } from '../client/ApiClient.js';
9
+ export default createProtocolClient;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Internal apiKey → capability exchange.
3
+ *
4
+ * Called by the `Ablo({...})` factory's `ready()` flow when the
5
+ * consumer passed `apiKey` without an explicit `capabilityToken` /
6
+ * `organizationId` / `user.id`. SDK calls `/auth/capability` once,
7
+ * server returns the scope + userMeta blobs (Phases 1A + 1B),
8
+ * SDK populates internal state from the response.
9
+ *
10
+ * Consumer never sees this happen. Same shape as Stripe / Anthropic
11
+ * SDKs hide their internal auth-handshake — the apiKey is the only
12
+ * credential the consumer touches.
13
+ */
14
+ /** Server response shape — matches Phase 1A + 1B wire output. */
15
+ export interface CapabilityExchangeResponse {
16
+ readonly capabilityId: string;
17
+ readonly token: string;
18
+ readonly expiresAt: string;
19
+ readonly organizationId: string;
20
+ readonly scope: {
21
+ readonly organizationId: string;
22
+ readonly syncGroups: readonly string[];
23
+ readonly operations: readonly string[];
24
+ readonly participantKind: 'user' | 'agent' | 'system';
25
+ readonly participantId: string;
26
+ };
27
+ readonly userMeta: Record<string, unknown>;
28
+ }
29
+ export interface ExchangeApiKeyRequest {
30
+ readonly apiKey: string;
31
+ readonly baseUrl: string;
32
+ readonly participantKind: 'user' | 'agent' | 'system';
33
+ readonly participantId?: string;
34
+ readonly syncGroups?: readonly string[];
35
+ readonly operations?: readonly string[];
36
+ readonly wideScope?: boolean;
37
+ readonly ttlSeconds: number;
38
+ readonly label?: string;
39
+ readonly userMeta?: Record<string, unknown>;
40
+ readonly fetch?: typeof fetch;
41
+ readonly timeoutMs?: number;
42
+ }
43
+ export declare function exchangeApiKey(options: ExchangeApiKeyRequest): Promise<CapabilityExchangeResponse>;
44
+ export interface IdentityResolveResponse {
45
+ readonly participantKind: 'user' | 'agent' | 'system';
46
+ readonly participantId: string;
47
+ readonly accountScope: string;
48
+ readonly syncGroups: readonly string[];
49
+ readonly userMeta: Record<string, unknown>;
50
+ }
51
+ export interface ResolveIdentityRequest {
52
+ readonly baseUrl: string;
53
+ readonly authToken?: string;
54
+ readonly fetch?: typeof fetch;
55
+ readonly timeoutMs?: number;
56
+ }
57
+ /**
58
+ * Resolve the caller's Ablo identity from the authenticated request
59
+ * context. Used by browser/session/capability flows where the SDK should
60
+ * not require a public `userId` prop just to open local storage.
61
+ */
62
+ export declare function resolveIdentity(options: ResolveIdentityRequest): Promise<IdentityResolveResponse>;
63
+ /**
64
+ * Capability-token refresh scheduler.
65
+ *
66
+ * Long-lived `@ablo/sync-engine` clients hold a server-issued capability
67
+ * token whose TTL (1h default) is shorter than typical browser sessions.
68
+ * Without proactive refresh, the WebSocket would either be force-closed
69
+ * by the server at expiry (code 1008) or fail its next reconnect with
70
+ * 401. Either way the user sees a mid-session disconnect.
71
+ *
72
+ * This scheduler keeps the token fresh transparently. Three triggers,
73
+ * one refresh path:
74
+ *
75
+ * 1. Proactive — `setTimeout` for `(expiresAtMs - bufferMs - now)`.
76
+ * 2. Visibility — on `document.visibilitychange→visible`, if the
77
+ * token is within the buffer window, refresh now.
78
+ * Defends against dormant-tab `setTimeout` throttling.
79
+ * 3. Reactive — caller invokes `.refreshNow()` on observed auth
80
+ * failure (WS close 1008/4001 etc).
81
+ *
82
+ * All three resolve through the same `inFlight` promise so concurrent
83
+ * triggers don't double-mint. On any successful refresh the new
84
+ * `expiresAtMs` is captured and trigger 1 is rescheduled.
85
+ *
86
+ * Buffer policy: `max(60s, ttl/10)` — for a 1h TTL that's 360s, which
87
+ * matches the AWS SDK / MSAL.js de-facto 5-minute standard while
88
+ * scaling sensibly for shorter TTLs.
89
+ */
90
+ export interface RefreshSchedulerOptions {
91
+ /** Initial absolute expiry, ms since epoch (server-supplied). */
92
+ readonly initialExpiresAtMs: number;
93
+ /**
94
+ * Performs the actual exchange. Returns the new expiry. Errors
95
+ * propagate to `onError`; the scheduler stays alive and retries on
96
+ * next trigger (no exponential backoff in v1 — most failures here are
97
+ * the user's apiKey being revoked, in which case retrying is futile).
98
+ */
99
+ readonly refresh: () => Promise<{
100
+ expiresAtMs: number;
101
+ }>;
102
+ /** Called on every successful refresh. */
103
+ readonly onRefreshed?: (info: {
104
+ expiresAtMs: number;
105
+ }) => void;
106
+ /** Called on every refresh failure. */
107
+ readonly onError?: (error: Error) => void;
108
+ /**
109
+ * Override the buffer (ms ahead of expiry to refresh). Defaults to
110
+ * `max(60_000, ttlMs * 0.1)`. Tests use a tiny value to exercise
111
+ * scheduling without burning real time.
112
+ */
113
+ readonly bufferMs?: number;
114
+ /**
115
+ * If true, install a `visibilitychange` listener on `document` that
116
+ * triggers a refresh when the tab becomes visible and the token is
117
+ * within the buffer window. No-op if `document` is undefined (Node).
118
+ * Default: true in browser-ish environments.
119
+ */
120
+ readonly attachVisibilityListener?: boolean;
121
+ /** Time source. Override in tests; defaults to `Date.now`. */
122
+ readonly now?: () => number;
123
+ /** Timer pair. Override in tests. */
124
+ readonly setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
125
+ readonly clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;
126
+ }
127
+ export interface RefreshScheduler {
128
+ /** Force a refresh now. Idempotent — concurrent calls share one promise. */
129
+ refreshNow(): Promise<{
130
+ expiresAtMs: number;
131
+ }>;
132
+ /** Stop scheduling. Safe to call multiple times. */
133
+ dispose(): void;
134
+ /** Current absolute expiry. Updated after each successful refresh. */
135
+ readonly expiresAtMs: number;
136
+ }
137
+ export declare function createRefreshScheduler(options: RefreshSchedulerOptions): RefreshScheduler;