@abloatai/ablo 0.7.0 → 0.9.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.
- package/CHANGELOG.md +72 -1
- package/README.md +80 -66
- package/dist/BaseSyncedStore.d.ts +73 -0
- package/dist/BaseSyncedStore.js +179 -5
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/SyncEngineContext.d.ts +2 -1
- package/dist/SyncEngineContext.js +5 -3
- package/dist/agent/session.js +6 -5
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +26 -36
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +259 -33
- package/dist/client/Ablo.js +276 -73
- package/dist/client/ApiClient.d.ts +52 -4
- package/dist/client/ApiClient.js +236 -66
- package/dist/client/auth.d.ts +21 -2
- package/dist/client/auth.js +77 -5
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +187 -79
- package/dist/client/createModelProxy.js +203 -68
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +63 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +19 -0
- package/dist/client/registerDataSource.js +59 -0
- package/dist/client/validateAbloOptions.d.ts +2 -1
- package/dist/client/validateAbloOptions.js +8 -7
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +92 -1
- package/dist/errorCodes.js +139 -7
- package/dist/errors.d.ts +54 -3
- package/dist/errors.js +192 -44
- package/dist/index.d.ts +23 -10
- package/dist/index.js +21 -8
- package/dist/keys/index.d.ts +76 -0
- package/dist/keys/index.js +171 -0
- package/dist/mutators/UndoManager.d.ts +86 -50
- package/dist/mutators/UndoManager.js +129 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +22 -14
- package/dist/react/AbloProvider.d.ts +23 -101
- package/dist/react/AbloProvider.js +61 -103
- package/dist/react/ClientSideSuspense.d.ts +1 -1
- package/dist/react/DefaultFallback.d.ts +1 -1
- package/dist/react/SyncGroupProvider.d.ts +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +3 -2
- package/dist/react/useAblo.d.ts +4 -4
- package/dist/react/useAblo.js +10 -5
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/react/useReactive.js +16 -3
- package/dist/schema/ddl.d.ts +26 -3
- package/dist/schema/ddl.js +152 -4
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.js +12 -0
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +7 -3
- package/dist/schema/serialize.js +6 -2
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +59 -0
- package/dist/source/adapter.js +19 -0
- package/dist/source/adapters/drizzle.d.ts +34 -0
- package/dist/source/adapters/drizzle.js +147 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +199 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +143 -0
- package/dist/source/contract.js +98 -0
- package/dist/source/index.d.ts +61 -10
- package/dist/source/index.js +98 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +56 -42
- package/dist/sync/ConnectionManager.d.ts +57 -1
- package/dist/sync/ConnectionManager.js +186 -11
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +241 -41
- package/dist/sync/NetworkProbe.d.ts +60 -18
- package/dist/sync/NetworkProbe.js +121 -23
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +113 -89
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/sync/participants.js +5 -2
- package/dist/transactions/TransactionQueue.js +13 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api-keys.md +5 -5
- package/docs/api.md +125 -65
- package/docs/audit.md +16 -9
- package/docs/cli.md +57 -47
- package/docs/client-behavior.md +54 -40
- package/docs/coordination.md +66 -80
- package/docs/data-sources.md +56 -34
- package/docs/examples/agent-human.md +74 -28
- package/docs/examples/ai-sdk-tool.md +29 -22
- package/docs/examples/existing-python-backend.md +41 -26
- package/docs/examples/nextjs.md +32 -17
- package/docs/examples/scoped-agent.md +43 -28
- package/docs/examples/server-agent.md +40 -15
- package/docs/guarantees.md +38 -27
- package/docs/identity.md +65 -59
- package/docs/index.md +30 -19
- package/docs/integration-guide.md +78 -78
- package/docs/interaction-model.md +43 -35
- package/docs/mcp/claude-code.md +11 -19
- package/docs/mcp/cursor.md +7 -25
- package/docs/mcp/windsurf.md +7 -20
- package/docs/mcp.md +103 -26
- package/docs/quickstart.md +63 -61
- package/docs/react.md +24 -16
- package/docs/roadmap.md +13 -13
- package/docs/schema-contract.md +111 -0
- package/docs/the-loop.md +21 -0
- package/examples/README.md +8 -4
- package/examples/data-source/README.md +10 -7
- package/examples/data-source/customer-server.ts +27 -25
- package/examples/data-source/run.ts +4 -3
- package/examples/quickstart.ts +1 -1
- package/llms.txt +55 -21
- package/package.json +48 -3
package/dist/ai-sdk/index.d.ts
CHANGED
|
@@ -1,67 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* middleware.
|
|
2
|
+
* Ablo + AI SDK tools.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* `streamText` / `generateText`:
|
|
4
|
+
* The base pattern is intentionally one object all the way down:
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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`:
|
|
6
|
+
* 1. AI SDK `inputSchema` describes what the model may send.
|
|
7
|
+
* 2. `ablo.<model>.update({ id, data, claim })` performs the write.
|
|
8
|
+
* 3. `claim.description` tells humans and other agents what the tool is doing.
|
|
21
9
|
*
|
|
22
10
|
* ```ts
|
|
23
|
-
* import {
|
|
24
|
-
* import {
|
|
25
|
-
* intentBroadcastMiddleware,
|
|
26
|
-
* coordinationContextMiddleware,
|
|
27
|
-
* } from '@abloatai/ablo/ai-sdk';
|
|
11
|
+
* import { tool, streamText } from 'ai';
|
|
12
|
+
* import { z } from 'zod';
|
|
28
13
|
*
|
|
29
|
-
* const
|
|
14
|
+
* const renameTask = tool({
|
|
15
|
+
* description: 'Rename a task.',
|
|
16
|
+
* inputSchema: z.object({
|
|
17
|
+
* id: z.string().describe('Task id'),
|
|
18
|
+
* title: z.string().describe('New task title'),
|
|
19
|
+
* description: z
|
|
20
|
+
* .string()
|
|
21
|
+
* .describe('Why this rename is being made'),
|
|
22
|
+
* }),
|
|
23
|
+
* execute: async ({ id, title, description }) => {
|
|
24
|
+
* await ablo.tasks.update({
|
|
25
|
+
* id,
|
|
26
|
+
* data: { title },
|
|
27
|
+
* wait: 'confirmed',
|
|
28
|
+
* claim: {
|
|
29
|
+
* field: 'title',
|
|
30
|
+
* action: 'renaming',
|
|
31
|
+
* description,
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
30
34
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* middleware: [
|
|
34
|
-
* coordinationContextMiddleware({ agent, target }),
|
|
35
|
-
* intentBroadcastMiddleware({ agent, target }),
|
|
36
|
-
* ],
|
|
35
|
+
* return { id, title };
|
|
36
|
+
* },
|
|
37
37
|
* });
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* tools: { ... },
|
|
44
|
-
* system: '...',
|
|
39
|
+
* await streamText({
|
|
40
|
+
* model,
|
|
41
|
+
* messages,
|
|
42
|
+
* tools: { renameTask },
|
|
45
43
|
* });
|
|
46
44
|
* ```
|
|
47
45
|
*
|
|
48
|
-
*
|
|
46
|
+
* That is the common case. A claim passed directly to `update` is acquired,
|
|
47
|
+
* attached to the write, and released by the SDK.
|
|
49
48
|
*
|
|
50
|
-
*
|
|
51
|
-
* import { wrapWithMultiplayer } from '@abloatai/ablo/ai-sdk';
|
|
49
|
+
* For multi-step tools, take one handle and release it when the tool is done:
|
|
52
50
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* const claim = await ablo.tasks.claim({
|
|
53
|
+
* id,
|
|
54
|
+
* action: 'rewriting',
|
|
55
|
+
* description: 'Rewriting the task brief before updating follow-up fields.',
|
|
56
|
+
* ttl: '2m',
|
|
57
57
|
* });
|
|
58
|
+
*
|
|
59
|
+
* try {
|
|
60
|
+
* await ablo.tasks.update({ id, data: { title }, claim });
|
|
61
|
+
* await ablo.tasks.update({ id, data: { description: brief }, claim });
|
|
62
|
+
* } finally {
|
|
63
|
+
* await claim.release();
|
|
64
|
+
* }
|
|
58
65
|
* ```
|
|
59
66
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
67
|
+
* `claim.state`, `claim.queue`, and `claim.reorder` are coordination reads and
|
|
68
|
+
* scheduler controls. They are useful for UI or operators, but normal AI tools
|
|
69
|
+
* should start with `update({ id, data, claim })` or a manual claim handle.
|
|
70
|
+
*
|
|
71
|
+
* `wrapWithMultiplayer` is optional. Use it when the whole model call is scoped
|
|
72
|
+
* to one entity before any tool is chosen; tool implementations stay exactly
|
|
73
|
+
* the same.
|
|
65
74
|
*/
|
|
66
75
|
export { intentBroadcastMiddleware, type IntentTarget, type IntentBroadcastMiddlewareOptions, } from './intent-broadcast.js';
|
|
67
76
|
export { coordinationContextMiddleware, type CoordinationContextMiddlewareOptions, } from './coordination-context.js';
|
package/dist/ai-sdk/index.js
CHANGED
|
@@ -1,67 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* middleware.
|
|
2
|
+
* Ablo + AI SDK tools.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* `streamText` / `generateText`:
|
|
4
|
+
* The base pattern is intentionally one object all the way down:
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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`:
|
|
6
|
+
* 1. AI SDK `inputSchema` describes what the model may send.
|
|
7
|
+
* 2. `ablo.<model>.update({ id, data, claim })` performs the write.
|
|
8
|
+
* 3. `claim.description` tells humans and other agents what the tool is doing.
|
|
21
9
|
*
|
|
22
10
|
* ```ts
|
|
23
|
-
* import {
|
|
24
|
-
* import {
|
|
25
|
-
* intentBroadcastMiddleware,
|
|
26
|
-
* coordinationContextMiddleware,
|
|
27
|
-
* } from '@abloatai/ablo/ai-sdk';
|
|
11
|
+
* import { tool, streamText } from 'ai';
|
|
12
|
+
* import { z } from 'zod';
|
|
28
13
|
*
|
|
29
|
-
* const
|
|
14
|
+
* const renameTask = tool({
|
|
15
|
+
* description: 'Rename a task.',
|
|
16
|
+
* inputSchema: z.object({
|
|
17
|
+
* id: z.string().describe('Task id'),
|
|
18
|
+
* title: z.string().describe('New task title'),
|
|
19
|
+
* description: z
|
|
20
|
+
* .string()
|
|
21
|
+
* .describe('Why this rename is being made'),
|
|
22
|
+
* }),
|
|
23
|
+
* execute: async ({ id, title, description }) => {
|
|
24
|
+
* await ablo.tasks.update({
|
|
25
|
+
* id,
|
|
26
|
+
* data: { title },
|
|
27
|
+
* wait: 'confirmed',
|
|
28
|
+
* claim: {
|
|
29
|
+
* field: 'title',
|
|
30
|
+
* action: 'renaming',
|
|
31
|
+
* description,
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
30
34
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* middleware: [
|
|
34
|
-
* coordinationContextMiddleware({ agent, target }),
|
|
35
|
-
* intentBroadcastMiddleware({ agent, target }),
|
|
36
|
-
* ],
|
|
35
|
+
* return { id, title };
|
|
36
|
+
* },
|
|
37
37
|
* });
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* tools: { ... },
|
|
44
|
-
* system: '...',
|
|
39
|
+
* await streamText({
|
|
40
|
+
* model,
|
|
41
|
+
* messages,
|
|
42
|
+
* tools: { renameTask },
|
|
45
43
|
* });
|
|
46
44
|
* ```
|
|
47
45
|
*
|
|
48
|
-
*
|
|
46
|
+
* That is the common case. A claim passed directly to `update` is acquired,
|
|
47
|
+
* attached to the write, and released by the SDK.
|
|
49
48
|
*
|
|
50
|
-
*
|
|
51
|
-
* import { wrapWithMultiplayer } from '@abloatai/ablo/ai-sdk';
|
|
49
|
+
* For multi-step tools, take one handle and release it when the tool is done:
|
|
52
50
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* const claim = await ablo.tasks.claim({
|
|
53
|
+
* id,
|
|
54
|
+
* action: 'rewriting',
|
|
55
|
+
* description: 'Rewriting the task brief before updating follow-up fields.',
|
|
56
|
+
* ttl: '2m',
|
|
57
57
|
* });
|
|
58
|
+
*
|
|
59
|
+
* try {
|
|
60
|
+
* await ablo.tasks.update({ id, data: { title }, claim });
|
|
61
|
+
* await ablo.tasks.update({ id, data: { description: brief }, claim });
|
|
62
|
+
* } finally {
|
|
63
|
+
* await claim.release();
|
|
64
|
+
* }
|
|
58
65
|
* ```
|
|
59
66
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
67
|
+
* `claim.state`, `claim.queue`, and `claim.reorder` are coordination reads and
|
|
68
|
+
* scheduler controls. They are useful for UI or operators, but normal AI tools
|
|
69
|
+
* should start with `update({ id, data, claim })` or a manual claim handle.
|
|
70
|
+
*
|
|
71
|
+
* `wrapWithMultiplayer` is optional. Use it when the whole model call is scoped
|
|
72
|
+
* to one entity before any tool is chosen; tool implementations stay exactly
|
|
73
|
+
* the same.
|
|
65
74
|
*/
|
|
66
75
|
export { intentBroadcastMiddleware, } from './intent-broadcast.js';
|
|
67
76
|
export { coordinationContextMiddleware, } from './coordination-context.js';
|
|
@@ -62,6 +62,11 @@ export interface IntentBroadcastMiddlewareOptions<R extends SchemaRecord = Schem
|
|
|
62
62
|
* `'edit'`, `'read'`, `'review'`, `'generate'`. Default `'edit'`.
|
|
63
63
|
*/
|
|
64
64
|
readonly action?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Peer-visible explanation of the specific work this model call is about to
|
|
67
|
+
* perform. Surfaces to other agents through `ActiveIntent.description`.
|
|
68
|
+
*/
|
|
69
|
+
readonly description?: string;
|
|
65
70
|
}
|
|
66
71
|
/**
|
|
67
72
|
* Build the middleware. When `agent` or `target` is null, returns a
|
|
@@ -30,16 +30,23 @@
|
|
|
30
30
|
export function intentBroadcastMiddleware(options) {
|
|
31
31
|
const { agent, target } = options;
|
|
32
32
|
const action = options.action ?? 'edit';
|
|
33
|
-
const
|
|
34
|
-
|
|
33
|
+
const description = options.description;
|
|
34
|
+
const openClaim = () => {
|
|
35
|
+
if (!agent || !target)
|
|
36
|
+
return null;
|
|
37
|
+
return agent.intents.claim({
|
|
35
38
|
type: target.entityType,
|
|
36
39
|
id: target.entityId,
|
|
37
40
|
path: target.path,
|
|
38
41
|
range: target.range,
|
|
39
42
|
field: target.field,
|
|
40
43
|
meta: target.meta,
|
|
41
|
-
}, {
|
|
42
|
-
|
|
44
|
+
}, {
|
|
45
|
+
reason: action,
|
|
46
|
+
description,
|
|
47
|
+
ttl: target.estimatedMs ?? 60_000,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
43
50
|
return {
|
|
44
51
|
specificationVersion: 'v3',
|
|
45
52
|
// The AI SDK's middleware contract passes a no-arg `doStream` /
|
package/dist/ai-sdk/wrap.d.ts
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* model with both multiplayer middlewares (intent broadcast +
|
|
4
|
-
* coordination context) in the right order.
|
|
2
|
+
* Optional model wrapper for entity-scoped turns.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
* the 90% case.
|
|
4
|
+
* Tool implementations do not change. Keep tools as normal AI SDK tools; use
|
|
5
|
+
* `ablo.<model>.update({ id, data, claim })` inside `execute`. This wrapper is
|
|
6
|
+
* only for the surrounding model call, when the UI already knows "this turn is
|
|
7
|
+
* about deck_abc" before the model chooses a tool.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (messages, tools, system prompt, provider options, onFinish, etc.).
|
|
9
|
+
* It declares one realtime claim while the model is generating and injects a
|
|
10
|
+
* short note if someone else is already working on the same target.
|
|
15
11
|
*
|
|
16
12
|
* ```ts
|
|
17
13
|
* const wrapped = wrapWithMultiplayer({
|
|
18
14
|
* model: anthropic('claude-opus-4-7'),
|
|
19
15
|
* agent,
|
|
20
16
|
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
17
|
+
* action: 'renaming',
|
|
18
|
+
* description: 'Renaming the deck title to match the project brief.',
|
|
21
19
|
* });
|
|
22
20
|
*
|
|
23
21
|
* const result = streamText({
|
|
@@ -46,6 +44,11 @@ export interface WrapWithMultiplayerOptions {
|
|
|
46
44
|
* Convention: `'edit'`, `'read'`, `'review'`, `'generate'`.
|
|
47
45
|
*/
|
|
48
46
|
readonly action?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Peer-visible explanation of the specific work this model call is about to
|
|
49
|
+
* perform. Other agents receive it in their coordination context.
|
|
50
|
+
*/
|
|
51
|
+
readonly description?: string;
|
|
49
52
|
/**
|
|
50
53
|
* Optional intentIds to exclude from the coordination-context
|
|
51
54
|
* read — typically the caller's own claim if they're composing
|
package/dist/ai-sdk/wrap.js
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* model with both multiplayer middlewares (intent broadcast +
|
|
4
|
-
* coordination context) in the right order.
|
|
2
|
+
* Optional model wrapper for entity-scoped turns.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
* the 90% case.
|
|
4
|
+
* Tool implementations do not change. Keep tools as normal AI SDK tools; use
|
|
5
|
+
* `ablo.<model>.update({ id, data, claim })` inside `execute`. This wrapper is
|
|
6
|
+
* only for the surrounding model call, when the UI already knows "this turn is
|
|
7
|
+
* about deck_abc" before the model chooses a tool.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (messages, tools, system prompt, provider options, onFinish, etc.).
|
|
9
|
+
* It declares one realtime claim while the model is generating and injects a
|
|
10
|
+
* short note if someone else is already working on the same target.
|
|
15
11
|
*
|
|
16
12
|
* ```ts
|
|
17
13
|
* const wrapped = wrapWithMultiplayer({
|
|
18
14
|
* model: anthropic('claude-opus-4-7'),
|
|
19
15
|
* agent,
|
|
20
16
|
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
17
|
+
* action: 'renaming',
|
|
18
|
+
* description: 'Renaming the deck title to match the project brief.',
|
|
21
19
|
* });
|
|
22
20
|
*
|
|
23
21
|
* const result = streamText({
|
|
@@ -33,12 +31,12 @@ import { wrapLanguageModel } from 'ai';
|
|
|
33
31
|
import { intentBroadcastMiddleware, } from './intent-broadcast.js';
|
|
34
32
|
import { coordinationContextMiddleware } from './coordination-context.js';
|
|
35
33
|
export function wrapWithMultiplayer(options) {
|
|
36
|
-
const { model, agent, target, action, excludeIntentIds, extraMiddleware } = options;
|
|
34
|
+
const { model, agent, target, action, description, excludeIntentIds, extraMiddleware } = options;
|
|
37
35
|
return wrapLanguageModel({
|
|
38
36
|
model,
|
|
39
37
|
middleware: [
|
|
40
38
|
coordinationContextMiddleware({ agent, target, excludeIntentIds }),
|
|
41
|
-
intentBroadcastMiddleware({ agent, target, action }),
|
|
39
|
+
intentBroadcastMiddleware({ agent, target, action, description }),
|
|
42
40
|
...(extraMiddleware ?? []),
|
|
43
41
|
],
|
|
44
42
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single mutable source for the SDK's active bearer credential.
|
|
3
|
+
*
|
|
4
|
+
* Every transport should read from this object at request/connect time:
|
|
5
|
+
* bootstrap HTTP, lazy query HTTP, identity/probe HTTP, and WebSocket URL
|
|
6
|
+
* auth. Token refresh writes here once; consumers observe the new value
|
|
7
|
+
* through their getter without being manually patched one by one.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
11
|
+
*
|
|
12
|
+
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
13
|
+
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
14
|
+
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
15
|
+
* credential out of the query string, which ALB access logs, proxies, and
|
|
16
|
+
* browser history capture. The server reads the token from the subprotocol and
|
|
17
|
+
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Shared with
|
|
18
|
+
* the sync-server so client and server can never drift on the wire format.
|
|
19
|
+
*/
|
|
20
|
+
export declare const WS_BEARER_SUBPROTOCOL_PREFIX = "ablo.bearer.";
|
|
21
|
+
export declare const WS_SYNC_SUBPROTOCOL = "ablo.sync.v1";
|
|
22
|
+
export interface AuthCredentialSource {
|
|
23
|
+
getAuthToken(): string | null;
|
|
24
|
+
setAuthToken(token: string | null | undefined): void;
|
|
25
|
+
authorizationHeader(): string | undefined;
|
|
26
|
+
withAuthHeaders(headers?: Record<string, string>): Record<string, string>;
|
|
27
|
+
applyAuthQueryParam(params: URLSearchParams, paramName?: string): void;
|
|
28
|
+
}
|
|
29
|
+
export type AuthTokenGetter = () => string | null | undefined;
|
|
30
|
+
export declare function createAuthCredentialSource(initialToken?: string | null): AuthCredentialSource;
|
|
31
|
+
export declare function resolveAuthToken(getAuthToken?: AuthTokenGetter, fallbackToken?: string | null): string | undefined;
|
|
32
|
+
export declare function authorizationHeaderForToken(token: string | null | undefined): string | undefined;
|
|
33
|
+
export declare function withAuthHeaders(getAuthToken: AuthTokenGetter | undefined, headers?: Record<string, string>, fallbackToken?: string | null): Record<string, string>;
|
|
34
|
+
export declare function applyAuthToQueryParams(params: URLSearchParams, getAuthToken: AuthTokenGetter | undefined, paramName?: string, fallbackToken?: string | null): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single mutable source for the SDK's active bearer credential.
|
|
3
|
+
*
|
|
4
|
+
* Every transport should read from this object at request/connect time:
|
|
5
|
+
* bootstrap HTTP, lazy query HTTP, identity/probe HTTP, and WebSocket URL
|
|
6
|
+
* auth. Token refresh writes here once; consumers observe the new value
|
|
7
|
+
* through their getter without being manually patched one by one.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
11
|
+
*
|
|
12
|
+
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
13
|
+
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
14
|
+
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
15
|
+
* credential out of the query string, which ALB access logs, proxies, and
|
|
16
|
+
* browser history capture. The server reads the token from the subprotocol and
|
|
17
|
+
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Shared with
|
|
18
|
+
* the sync-server so client and server can never drift on the wire format.
|
|
19
|
+
*/
|
|
20
|
+
export const WS_BEARER_SUBPROTOCOL_PREFIX = 'ablo.bearer.';
|
|
21
|
+
export const WS_SYNC_SUBPROTOCOL = 'ablo.sync.v1';
|
|
22
|
+
export function createAuthCredentialSource(initialToken) {
|
|
23
|
+
let authToken = normalizeToken(initialToken);
|
|
24
|
+
return {
|
|
25
|
+
getAuthToken: () => authToken,
|
|
26
|
+
setAuthToken(token) {
|
|
27
|
+
authToken = normalizeToken(token);
|
|
28
|
+
},
|
|
29
|
+
authorizationHeader() {
|
|
30
|
+
return authorizationHeaderForToken(authToken);
|
|
31
|
+
},
|
|
32
|
+
withAuthHeaders(headers = {}) {
|
|
33
|
+
const authorization = authorizationHeaderForToken(authToken);
|
|
34
|
+
return authorization ? { ...headers, Authorization: authorization } : { ...headers };
|
|
35
|
+
},
|
|
36
|
+
applyAuthQueryParam(params, paramName = 'authorization') {
|
|
37
|
+
applyAuthToQueryParams(params, () => authToken, paramName);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function resolveAuthToken(getAuthToken, fallbackToken) {
|
|
42
|
+
return normalizeToken(getAuthToken?.() ?? fallbackToken) ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
export function authorizationHeaderForToken(token) {
|
|
45
|
+
const normalized = normalizeToken(token);
|
|
46
|
+
return normalized ? `Bearer ${normalized}` : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function withAuthHeaders(getAuthToken, headers = {}, fallbackToken) {
|
|
49
|
+
const authorization = authorizationHeaderForToken(resolveAuthToken(getAuthToken, fallbackToken));
|
|
50
|
+
return authorization ? { ...headers, Authorization: authorization } : { ...headers };
|
|
51
|
+
}
|
|
52
|
+
export function applyAuthToQueryParams(params, getAuthToken, paramName = 'authorization', fallbackToken) {
|
|
53
|
+
const authorization = authorizationHeaderForToken(resolveAuthToken(getAuthToken, fallbackToken));
|
|
54
|
+
if (authorization) {
|
|
55
|
+
params.set(paramName, authorization);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function normalizeToken(token) {
|
|
59
|
+
if (!token)
|
|
60
|
+
return null;
|
|
61
|
+
const trimmed = token.trim();
|
|
62
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
63
|
+
}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -11,21 +11,8 @@
|
|
|
11
11
|
* SDKs hide their internal auth-handshake — the apiKey is the only
|
|
12
12
|
* credential the consumer touches.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
export
|
|
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
|
-
}
|
|
14
|
+
import { type CapabilityExchangeResponse, type IdentityResolveResponse } from './schemas.js';
|
|
15
|
+
export type { CapabilityExchangeResponse, IdentityResolveResponse } from './schemas.js';
|
|
29
16
|
export interface ExchangeApiKeyRequest {
|
|
30
17
|
readonly apiKey: string;
|
|
31
18
|
readonly baseUrl: string;
|
|
@@ -41,13 +28,6 @@ export interface ExchangeApiKeyRequest {
|
|
|
41
28
|
readonly timeoutMs?: number;
|
|
42
29
|
}
|
|
43
30
|
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
31
|
export interface ResolveIdentityRequest {
|
|
52
32
|
readonly baseUrl: string;
|
|
53
33
|
readonly authToken?: string;
|
package/dist/auth/index.js
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* SDKs hide their internal auth-handshake — the apiKey is the only
|
|
12
12
|
* credential the consumer touches.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { parseCapabilityExchangeResponse, parseIdentityResolveResponse, } from './schemas.js';
|
|
15
|
+
import { AbloAuthenticationError, hasWireCode, translateHttpError } from '../errors.js';
|
|
15
16
|
export async function exchangeApiKey(options) {
|
|
16
17
|
if (!options.apiKey) {
|
|
17
18
|
throw new AbloAuthenticationError('apiKey is required for capability exchange', { code: 'apikey_missing' });
|
|
@@ -59,33 +60,18 @@ export async function exchangeApiKey(options) {
|
|
|
59
60
|
catch {
|
|
60
61
|
// ignore — server returned non-JSON error
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
function isCapabilityExchangeResponse(raw) {
|
|
75
|
-
if (!raw || typeof raw !== 'object')
|
|
76
|
-
return false;
|
|
77
|
-
const o = raw;
|
|
78
|
-
if (typeof o.token !== 'string')
|
|
79
|
-
return false;
|
|
80
|
-
if (typeof o.expiresAt !== 'string')
|
|
81
|
-
return false;
|
|
82
|
-
if (typeof o.organizationId !== 'string')
|
|
83
|
-
return false;
|
|
84
|
-
if (typeof o.scope !== 'object' || o.scope === null)
|
|
85
|
-
return false;
|
|
86
|
-
if (typeof o.userMeta !== 'object' || o.userMeta === null)
|
|
87
|
-
return false;
|
|
88
|
-
return true;
|
|
63
|
+
// Route through the canonical wire-error translator so the server's
|
|
64
|
+
// envelope (`code` + `message` + `doc_url`) propagates verbatim and maps to
|
|
65
|
+
// the right AbloError subclass — instead of the legacy `error`/`reason`
|
|
66
|
+
// shape this used to read (which the server no longer emits, collapsing
|
|
67
|
+
// every failure to a generic code with an empty message). Fall back to
|
|
68
|
+
// `exchange_failed` only when the body carried no recognizable code.
|
|
69
|
+
const requestId = response.headers.get('x-request-id') ?? undefined;
|
|
70
|
+
throw hasWireCode(body)
|
|
71
|
+
? translateHttpError(response.status, body, requestId)
|
|
72
|
+
: new AbloAuthenticationError(`apiKey exchange rejected (${response.status})`, { code: 'exchange_failed', httpStatus: response.status });
|
|
73
|
+
}
|
|
74
|
+
return parseCapabilityExchangeResponse(await response.json());
|
|
89
75
|
}
|
|
90
76
|
/**
|
|
91
77
|
* Resolve the caller's Ablo identity from the authenticated request
|
|
@@ -112,7 +98,6 @@ export async function resolveIdentity(options) {
|
|
|
112
98
|
response = await fetcher(url, {
|
|
113
99
|
method: 'GET',
|
|
114
100
|
headers,
|
|
115
|
-
credentials: 'include',
|
|
116
101
|
signal: controller.signal,
|
|
117
102
|
});
|
|
118
103
|
}
|
|
@@ -130,13 +115,18 @@ export async function resolveIdentity(options) {
|
|
|
130
115
|
catch {
|
|
131
116
|
// ignore non-JSON auth errors
|
|
132
117
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
118
|
+
// Canonical envelope translation (see `exchangeApiKey` above). This is what
|
|
119
|
+
// surfaces the sync-server's precise auth diagnosis — e.g.
|
|
120
|
+
// `jwt_issuer_untrusted` with its full message — to the SDK consumer,
|
|
121
|
+
// instead of collapsing every 401 to `identity_resolve_failed` with an
|
|
122
|
+
// empty reason because the old parser looked for `error`/`reason` keys the
|
|
123
|
+
// server doesn't emit.
|
|
124
|
+
const requestId = response.headers.get('x-request-id') ?? undefined;
|
|
125
|
+
throw hasWireCode(body)
|
|
126
|
+
? translateHttpError(response.status, body, requestId)
|
|
127
|
+
: new AbloAuthenticationError(`identity resolve rejected (${response.status})`, { code: 'identity_resolve_failed', httpStatus: response.status });
|
|
128
|
+
}
|
|
129
|
+
return parseIdentityResolveResponse(await response.json());
|
|
140
130
|
}
|
|
141
131
|
const DEFAULT_BUFFER_FLOOR_MS = 60_000;
|
|
142
132
|
const DEFAULT_BUFFER_RATIO = 0.1;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AuthTokenSchema: z.ZodString;
|
|
3
|
+
export declare const CapabilityExchangeResponseSchema: z.ZodObject<{
|
|
4
|
+
capabilityId: z.ZodString;
|
|
5
|
+
token: z.ZodString;
|
|
6
|
+
expiresAt: z.ZodString;
|
|
7
|
+
organizationId: z.ZodString;
|
|
8
|
+
scope: z.ZodObject<{
|
|
9
|
+
organizationId: z.ZodString;
|
|
10
|
+
syncGroups: z.ZodArray<z.ZodString>;
|
|
11
|
+
operations: z.ZodArray<z.ZodString>;
|
|
12
|
+
participantKind: z.ZodEnum<{
|
|
13
|
+
user: "user";
|
|
14
|
+
agent: "agent";
|
|
15
|
+
system: "system";
|
|
16
|
+
}>;
|
|
17
|
+
participantId: z.ZodString;
|
|
18
|
+
}, z.core.$loose>;
|
|
19
|
+
userMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
20
|
+
}, z.core.$loose>;
|
|
21
|
+
export type CapabilityExchangeResponse = z.infer<typeof CapabilityExchangeResponseSchema>;
|
|
22
|
+
export declare const IdentityResolveResponseSchema: z.ZodObject<{
|
|
23
|
+
participantKind: z.ZodEnum<{
|
|
24
|
+
user: "user";
|
|
25
|
+
agent: "agent";
|
|
26
|
+
system: "system";
|
|
27
|
+
}>;
|
|
28
|
+
participantId: z.ZodString;
|
|
29
|
+
accountScope: z.ZodString;
|
|
30
|
+
syncGroups: z.ZodArray<z.ZodString>;
|
|
31
|
+
userMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
32
|
+
}, z.core.$loose>;
|
|
33
|
+
export type IdentityResolveResponse = z.infer<typeof IdentityResolveResponseSchema>;
|
|
34
|
+
export declare function parseCapabilityExchangeResponse(raw: unknown): CapabilityExchangeResponse;
|
|
35
|
+
export declare function parseIdentityResolveResponse(raw: unknown): IdentityResolveResponse;
|