@ably/ai-transport 0.1.0 → 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.
- package/README.md +93 -111
- package/dist/ably-ai-transport.js +2401 -1387
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +116 -42
- package/dist/core/agent.d.ts +44 -0
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +24 -24
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +10 -12
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -2
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +470 -119
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +55 -0
- package/dist/core/transport/headers.d.ts +121 -14
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +44 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +76 -0
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +523 -109
- package/dist/core/transport/types/agent.d.ts +375 -0
- package/dist/core/transport/types/client.d.ts +201 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +357 -0
- package/dist/core/transport/types/view.d.ts +249 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +390 -84
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +27 -10
- package/dist/index.d.ts +8 -9
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1365 -1010
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +37 -0
- package/dist/react/contexts/client-session-provider.d.ts +56 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +13 -12
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +81 -50
- package/dist/utils.d.ts +48 -71
- package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +50 -0
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +7 -20
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +62 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -8
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +5 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +84 -0
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +41 -24
- package/dist/vercel/transport/index.d.ts +24 -20
- package/dist/vercel/transport/run-output-stream.d.ts +54 -0
- package/dist/version.d.ts +2 -0
- package/package.json +31 -24
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +92 -0
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +202 -105
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +114 -107
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +56 -6
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +505 -126
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +1085 -0
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +780 -0
- package/src/core/transport/decode-fold.ts +101 -0
- package/src/core/transport/headers.ts +234 -22
- package/src/core/transport/index.ts +27 -27
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +271 -0
- package/src/core/transport/pipe-stream.ts +63 -39
- package/src/core/transport/run-manager.ts +243 -0
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +1293 -308
- package/src/core/transport/types/agent.ts +434 -0
- package/src/core/transport/types/client.ts +247 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +393 -0
- package/src/core/transport/types/view.ts +288 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +1229 -450
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +29 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +86 -42
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +222 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +24 -13
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +178 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +138 -96
- package/src/utils.ts +83 -131
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +85 -0
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +28 -21
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +191 -0
- package/src/vercel/codec/tool-transitions.ts +3 -14
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +7 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +44 -66
- package/src/vercel/react/use-message-sync.ts +75 -39
- package/src/vercel/run-end-reason.ts +157 -0
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +380 -98
- package/src/vercel/transport/index.ts +38 -37
- package/src/vercel/transport/run-output-stream.ts +169 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/codec/decoder.d.ts +0 -22
- package/dist/vercel/codec/encoder.d.ts +0 -41
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/codec/decoder.ts +0 -618
- package/src/vercel/codec/encoder.ts +0 -410
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- package/src/vercel/tool-events.ts +0 -53
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AgentSession, AgentSessionOptions, ClientSession, ClientSessionOptions } from '../../core/transport/types.js';
|
|
2
|
+
import { VercelInput, VercelOutput, VercelProjection } from '../codec/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* Vercel AI SDK transport wrappers that pre-bind the UIMessageCodec.
|
|
4
5
|
*
|
|
@@ -6,34 +7,37 @@ import { ClientTransport, ClientTransportOptions, ServerTransport, ServerTranspo
|
|
|
6
7
|
* explicitly when using the Vercel AI SDK integration.
|
|
7
8
|
*
|
|
8
9
|
* ```ts
|
|
9
|
-
* import {
|
|
10
|
+
* import { createClientSession } from '@ably/ai-transport/vercel';
|
|
10
11
|
*
|
|
11
|
-
* const
|
|
12
|
+
* const session = createClientSession({ client, channelName: 'ai:demo' });
|
|
13
|
+
* await session.connect();
|
|
12
14
|
* ```
|
|
13
15
|
*/
|
|
14
16
|
export type { ChatTransport, ChatTransportOptions, SendMessagesRequestContext } from './chat-transport.js';
|
|
15
17
|
export { createChatTransport } from './chat-transport.js';
|
|
16
18
|
import type * as AI from 'ai';
|
|
17
|
-
/** Core client
|
|
18
|
-
type CoreClientOpts =
|
|
19
|
-
/** Options for creating a Vercel client
|
|
20
|
-
export type
|
|
21
|
-
/** Options for creating a Vercel
|
|
22
|
-
export type
|
|
23
|
-
export declare const DEFAULT_VERCEL_API = "/api/chat";
|
|
19
|
+
/** Core client session options with Vercel AI SDK types pre-applied. */
|
|
20
|
+
type CoreClientOpts = ClientSessionOptions<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
21
|
+
/** Options for creating a Vercel client session. Same as core options but without the codec field. */
|
|
22
|
+
export type VercelClientSessionOptions = Omit<CoreClientOpts, 'codec'>;
|
|
23
|
+
/** Options for creating a Vercel agent session. Same as core options but without the codec field. */
|
|
24
|
+
export type VercelAgentSessionOptions = Omit<AgentSessionOptions<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>, 'codec'>;
|
|
24
25
|
/**
|
|
25
|
-
* Create a client-side
|
|
26
|
+
* Create a client-side session pre-configured with the Vercel AI SDK codec.
|
|
26
27
|
*
|
|
27
|
-
* Equivalent to calling the core `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* Equivalent to calling the core `createClientSession` with `codec: UIMessageCodec`.
|
|
29
|
+
* The core session is a pure Ably-channel transport — it never sends HTTP.
|
|
30
|
+
* To wake a serverless agent over HTTP, POST `run.toInvocation().toJSON()`
|
|
31
|
+
* yourself, or use `createChatTransport` (which does it for useChat parity).
|
|
32
|
+
* @param options - Configuration for the client session (codec is provided automatically).
|
|
33
|
+
* @returns A new {@link ClientSession} for Vercel AI SDK UIMessage/UIMessageChunk types.
|
|
30
34
|
*/
|
|
31
|
-
export declare const
|
|
35
|
+
export declare const createClientSession: (options: VercelClientSessionOptions) => ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
32
36
|
/**
|
|
33
|
-
* Create
|
|
37
|
+
* Create an agent (server-side) session pre-configured with the Vercel AI SDK codec.
|
|
34
38
|
*
|
|
35
|
-
* Equivalent to calling the core `
|
|
36
|
-
* @param options - Configuration for the
|
|
37
|
-
* @returns A new {@link
|
|
39
|
+
* Equivalent to calling the core `createAgentSession` with `codec: UIMessageCodec`.
|
|
40
|
+
* @param options - Configuration for the agent session (codec is provided automatically).
|
|
41
|
+
* @returns A new {@link AgentSession} for Vercel AI SDK UIMessage/UIMessageChunk types.
|
|
38
42
|
*/
|
|
39
|
-
export declare const
|
|
43
|
+
export declare const createAgentSession: (options: VercelAgentSessionOptions) => AgentSession<VercelOutput, VercelProjection, AI.UIMessage>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ClientSession } from '../../core/transport/types.js';
|
|
2
|
+
import { VercelInput, VercelOutput, VercelProjection } from '../codec/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Vercel-owned per-run output stream.
|
|
5
|
+
*
|
|
6
|
+
* Builds the `ReadableStream<UIMessageChunk>` that `useChat` consumes by
|
|
7
|
+
* subscribing to the session Tree's `output` and `run` events for a single
|
|
8
|
+
* run. Streaming is a useChat-integration concern, so it lives in the Vercel
|
|
9
|
+
* layer rather than the generic core: the core Tree is the fan-out point, and
|
|
10
|
+
* this projects its events into the shape `useChat` expects.
|
|
11
|
+
*
|
|
12
|
+
* Close semantics — the stream the consumer reads ends when:
|
|
13
|
+
* - a **terminal chunk** (`finish` / `error` / `abort`) is folded for the run.
|
|
14
|
+
* This is the signal `useChat`'s `sendAutomaticallyWhen` waits for, and it
|
|
15
|
+
* fires even when the run merely *suspends* for a tool call (a tool-calls
|
|
16
|
+
* `finish` ends the consumer stream while the core run stays alive in the
|
|
17
|
+
* Tree for the continuation); or
|
|
18
|
+
* - the run reaches `run-end`, which is always terminal (safety net for a run
|
|
19
|
+
* that ends without emitting a terminal chunk). A `run-suspend` keeps the
|
|
20
|
+
* core run alive and does not close the consumer stream.
|
|
21
|
+
*
|
|
22
|
+
* It errors when the session emits a non-fatal `error` (e.g. channel
|
|
23
|
+
* continuity loss, or an agent-reported mid-run error), so the consumer's
|
|
24
|
+
* reader rejects rather than hanging.
|
|
25
|
+
*/
|
|
26
|
+
import type * as AI from 'ai';
|
|
27
|
+
type VercelSession = ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
28
|
+
/** A consumer-facing run output stream plus the handle to close it externally. */
|
|
29
|
+
interface RunOutputStream {
|
|
30
|
+
/** The stream of decoded outputs for the run, as `useChat` consumes it. */
|
|
31
|
+
stream: ReadableStream<VercelOutput>;
|
|
32
|
+
/** Close the stream now (e.g. on local cancel). Idempotent. */
|
|
33
|
+
close: () => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a consumer-facing output stream for a send, sourced from the session
|
|
37
|
+
* Tree's events. See the module docs for close/error semantics. The returned
|
|
38
|
+
* `close` lets the caller settle the stream for conditions the Tree doesn't
|
|
39
|
+
* surface (local cancel). Session errors are wired internally to error the
|
|
40
|
+
* stream.
|
|
41
|
+
*
|
|
42
|
+
* Outputs route PURELY by the triggering input's codec-message-id — the key the
|
|
43
|
+
* client owns from send time, before the agent mints the runId. The agent's
|
|
44
|
+
* minted runId is supplied as a promise so the run-end safety-net can still
|
|
45
|
+
* close the stream once it resolves.
|
|
46
|
+
* @param session - The Vercel client session whose Tree to observe.
|
|
47
|
+
* @param runId - The agent-minted runId, resolved when run-start is observed.
|
|
48
|
+
* Used only by the run-end safety-net; routing keys on `inputCodecMessageId`.
|
|
49
|
+
* @param inputCodecMessageId - The triggering input's codec-message-id. An
|
|
50
|
+
* output routes to this stream when it carries this id.
|
|
51
|
+
* @returns The stream and its external close handle.
|
|
52
|
+
*/
|
|
53
|
+
export declare const createRunOutputStream: (session: VercelSession, runId: Promise<string>, inputCodecMessageId: string) => RunOutputStream;
|
|
54
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ably/ai-transport",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Ably transport and codecs for building AI applications with Ably.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/ably-ai-transport.umd.cjs",
|
|
@@ -36,26 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "npm run build:clean && npm run build:core && npm run build:react && npm run build:vercel && npm run build:vercel-react",
|
|
41
|
-
"build:clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
42
|
-
"build:core": "vite build --config ./src/vite.config.ts",
|
|
43
|
-
"build:react": "vite build --config ./src/react/vite.config.ts --emptyOutDir",
|
|
44
|
-
"build:vercel": "vite build --config ./src/vercel/vite.config.ts --emptyOutDir",
|
|
45
|
-
"build:vercel-react": "vite build --config ./src/vercel/react/vite.config.ts --emptyOutDir",
|
|
46
|
-
"prepare": "npm run build",
|
|
47
|
-
"lint": "eslint .",
|
|
48
|
-
"lint:fix": "eslint --fix .; (npm run format > /dev/null)",
|
|
49
|
-
"format": "prettier --list-different --write .",
|
|
50
|
-
"format:check": "prettier --check .",
|
|
51
|
-
"typecheck": "tsc --noEmit",
|
|
52
|
-
"test": "vitest run",
|
|
53
|
-
"test:integration": "vitest run --config vitest.config.integration.ts",
|
|
54
|
-
"check:error-codes": "tsx scripts/validate-error-codes.ts",
|
|
55
|
-
"precommit": "npm run format:check && npm run lint && npm run typecheck",
|
|
56
|
-
"docs": "typedoc",
|
|
57
|
-
"docs:lint": "typedoc --emit none"
|
|
58
|
-
},
|
|
59
39
|
"files": [
|
|
60
40
|
"dist/**",
|
|
61
41
|
"src/**",
|
|
@@ -80,10 +60,17 @@
|
|
|
80
60
|
},
|
|
81
61
|
"homepage": "https://github.com/ably/ably-ai-transport-js#readme",
|
|
82
62
|
"engines": {
|
|
83
|
-
"node": ">=
|
|
63
|
+
"node": ">=22.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devEngines": {
|
|
66
|
+
"packageManager": {
|
|
67
|
+
"name": "pnpm",
|
|
68
|
+
"version": "11.3.0",
|
|
69
|
+
"onFail": "download"
|
|
70
|
+
}
|
|
84
71
|
},
|
|
85
72
|
"peerDependencies": {
|
|
86
|
-
"ably": "^2.
|
|
73
|
+
"ably": "^2.23.0",
|
|
87
74
|
"ai": "^6",
|
|
88
75
|
"react": "^18.0.0 || ^19.0.0"
|
|
89
76
|
},
|
|
@@ -101,6 +88,7 @@
|
|
|
101
88
|
"@eslint/eslintrc": "^3.3.0",
|
|
102
89
|
"@eslint/js": "^10.0.1",
|
|
103
90
|
"@testing-library/react": "^16.3.2",
|
|
91
|
+
"@types/node": "^25.0.0",
|
|
104
92
|
"@types/react": "^19.2.14",
|
|
105
93
|
"@typescript-eslint/eslint-plugin": "^8.58.2",
|
|
106
94
|
"@typescript-eslint/parser": "^8.58.2",
|
|
@@ -125,5 +113,24 @@
|
|
|
125
113
|
"vite": "^8.0.0",
|
|
126
114
|
"vite-plugin-dts": "^4.5.4",
|
|
127
115
|
"vitest": "^4.1.0"
|
|
116
|
+
},
|
|
117
|
+
"scripts": {
|
|
118
|
+
"build": "pnpm run build:clean && pnpm run build:core && pnpm run build:react && pnpm run build:vercel && pnpm run build:vercel-react",
|
|
119
|
+
"build:clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
120
|
+
"build:core": "vite build --config ./src/vite.config.ts",
|
|
121
|
+
"build:react": "vite build --config ./src/react/vite.config.ts --emptyOutDir",
|
|
122
|
+
"build:vercel": "vite build --config ./src/vercel/vite.config.ts --emptyOutDir",
|
|
123
|
+
"build:vercel-react": "vite build --config ./src/vercel/react/vite.config.ts --emptyOutDir",
|
|
124
|
+
"lint": "eslint .",
|
|
125
|
+
"lint:fix": "eslint --fix .; (pnpm run format > /dev/null)",
|
|
126
|
+
"format": "prettier --list-different --write .",
|
|
127
|
+
"format:check": "prettier --check .",
|
|
128
|
+
"typecheck": "tsc --noEmit",
|
|
129
|
+
"test": "vitest run",
|
|
130
|
+
"test:integration": "vitest run --config vitest.config.integration.ts",
|
|
131
|
+
"check:error-codes": "tsx scripts/validate-error-codes.ts",
|
|
132
|
+
"precommit": "pnpm run format:check && pnpm run lint && pnpm run typecheck",
|
|
133
|
+
"docs": "typedoc",
|
|
134
|
+
"docs:lint": "typedoc --emit none"
|
|
128
135
|
}
|
|
129
|
-
}
|
|
136
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared constants used by both codec and transport layers.
|
|
3
3
|
*
|
|
4
|
-
* Header constants define the
|
|
5
|
-
* name constants define the
|
|
4
|
+
* Header constants define the transport wire header names. Message and event
|
|
5
|
+
* name constants define the session lifecycle signals on the channel.
|
|
6
6
|
*
|
|
7
7
|
* These live at the top level (not in codec/ or transport/) because both
|
|
8
8
|
* layers need them — the codec core reads/writes stream and status headers,
|
|
9
|
-
* while the transport layer reads/writes
|
|
9
|
+
* while the transport layer reads/writes run, cancel, and role headers.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
@@ -14,91 +14,164 @@
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
16
|
/** Header: whether this Ably message uses streaming (message appends) or is discrete. Always "true" or "false". */
|
|
17
|
-
export const HEADER_STREAM = '
|
|
17
|
+
export const HEADER_STREAM = 'stream';
|
|
18
18
|
|
|
19
|
-
/** Header: lifecycle status of a streamed message. Only set when
|
|
20
|
-
export const HEADER_STATUS = '
|
|
19
|
+
/** Header: lifecycle status of a streamed message. Only set when stream is "true". One of "streaming", "complete", or "cancelled". */
|
|
20
|
+
export const HEADER_STATUS = 'status';
|
|
21
21
|
|
|
22
22
|
/** Header: stream identity. Set by the encoder on every streamed message; read by the decoder to correlate streams. */
|
|
23
|
-
export const HEADER_STREAM_ID = '
|
|
23
|
+
export const HEADER_STREAM_ID = 'stream-id';
|
|
24
24
|
|
|
25
25
|
/** Header: marks a message as a discrete message part (from writeMessages). Set by publishDiscreteBatch; not set on lifecycle events from publishDiscrete. */
|
|
26
|
-
export const HEADER_DISCRETE = '
|
|
26
|
+
export const HEADER_DISCRETE = 'discrete';
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
|
-
// Identity headers (used by transport for
|
|
29
|
+
// Identity headers (used by transport for run correlation)
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
|
-
/** Header:
|
|
33
|
-
export const
|
|
32
|
+
/** Header: run correlation ID. Set on every agent-published message and on continuation client inputs, but omitted from the originating fresh client input (the agent mints the run-id at run-start). */
|
|
33
|
+
export const HEADER_RUN_ID = 'run-id';
|
|
34
|
+
|
|
35
|
+
/** Header: invocation correlation ID; identifies a specific invocation under a run. Agent-minted and stamped by the agent on every event it publishes for the invocation — run lifecycle (run-start/resume/suspend/end) and assistant outputs. Never set by the client on its input. */
|
|
36
|
+
export const HEADER_INVOCATION_ID = 'invocation-id';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Header: per-event identifier stamped by the client on every
|
|
40
|
+
* client-published event in a send — user-message events AND amend
|
|
41
|
+
* events (tool-approval responses, client tool outputs). Distinct from
|
|
42
|
+
* `codec-message-id` so it survives edits/retries that reuse the same
|
|
43
|
+
* codec-message-id, and so amend events that target an existing message can
|
|
44
|
+
* carry their own per-send identity. The invocation body lists every
|
|
45
|
+
* inputEventId the agent must observe on the channel before starting LLM
|
|
46
|
+
* work — see `Run.start()`'s input-event lookup.
|
|
47
|
+
*/
|
|
48
|
+
export const HEADER_EVENT_ID = 'event-id';
|
|
34
49
|
|
|
35
50
|
/** Header: message identity. Assigned per message (user or assistant). Used for optimistic reconciliation on the client. */
|
|
36
|
-
export const
|
|
51
|
+
export const HEADER_CODEC_MESSAGE_ID = 'codec-message-id';
|
|
37
52
|
|
|
38
|
-
/** Header: clientId of the user who initiated the
|
|
39
|
-
export const
|
|
53
|
+
/** Header: clientId of the user who initiated the run. Stamped by the client on its user input and re-stamped by the agent on the run's lifecycle and stream messages. */
|
|
54
|
+
export const HEADER_RUN_CLIENT_ID = 'run-client-id';
|
|
40
55
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Header: clientId of the input event (the `ai-input`) that drove the
|
|
58
|
+
* current invocation. The agent reads the publisher's Ably-level `clientId`
|
|
59
|
+
* from the triggering input event on the channel and re-stamps it as
|
|
60
|
+
* `input-client-id` on every event it publishes for that invocation
|
|
61
|
+
* (run lifecycle and assistant outputs). May differ from
|
|
62
|
+
* `run-client-id` on continuation invocations driven by an input
|
|
63
|
+
* from a non-owner (e.g. a tool-result publish from a different client).
|
|
64
|
+
* Not stamped on `ai-input` events themselves — the wire publisher's
|
|
65
|
+
* Ably `clientId` already conveys that.
|
|
66
|
+
*/
|
|
67
|
+
export const HEADER_INPUT_CLIENT_ID = 'input-client-id';
|
|
43
68
|
|
|
44
|
-
/** Header:
|
|
45
|
-
export const
|
|
69
|
+
/** Header: message role (e.g. "user", "assistant"). */
|
|
70
|
+
export const HEADER_ROLE = 'role';
|
|
46
71
|
|
|
47
72
|
// ---------------------------------------------------------------------------
|
|
48
|
-
//
|
|
73
|
+
// Fork / branching headers
|
|
49
74
|
// ---------------------------------------------------------------------------
|
|
50
75
|
|
|
51
|
-
/** Header:
|
|
52
|
-
export const
|
|
53
|
-
|
|
54
|
-
/** Header: cancel all turns belonging to the sender's clientId. */
|
|
55
|
-
export const HEADER_CANCEL_OWN = 'x-ably-cancel-own';
|
|
76
|
+
/** Header: the codec-message-id of the immediately preceding message in this branch. */
|
|
77
|
+
export const HEADER_PARENT = 'parent';
|
|
56
78
|
|
|
57
|
-
/** Header:
|
|
58
|
-
export const
|
|
79
|
+
/** Header: the codec-message-id of the message this one replaces (creates a fork). */
|
|
80
|
+
export const HEADER_FORK_OF = 'fork-of';
|
|
59
81
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Header: the codec-message-id of the assistant message this run regenerates.
|
|
84
|
+
*
|
|
85
|
+
* Stamped on the regenerate wire (and echoed on `run-start`) when the
|
|
86
|
+
* client requested a regeneration. A regenerate run parents at the SAME input
|
|
87
|
+
* node as the reply it regenerates, so it joins that input's reply runs as a
|
|
88
|
+
* same-parent sibling (no fork-of). The View consults this header to resolve
|
|
89
|
+
* the message-level sibling group and to drop the regenerated message from
|
|
90
|
+
* earlier Runs in the visible chain (Spec: AIT-CT13d).
|
|
91
|
+
*/
|
|
92
|
+
export const HEADER_MSG_REGENERATE = 'msg-regenerate';
|
|
62
93
|
|
|
63
94
|
// ---------------------------------------------------------------------------
|
|
64
|
-
//
|
|
95
|
+
// Run lifecycle headers
|
|
65
96
|
// ---------------------------------------------------------------------------
|
|
66
97
|
|
|
67
|
-
/** Header:
|
|
68
|
-
export const
|
|
98
|
+
/** Header: reason a run ended (on ai-run-end messages). */
|
|
99
|
+
export const HEADER_RUN_REASON = 'run-reason';
|
|
69
100
|
|
|
70
|
-
/**
|
|
71
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Header: the `codec-message-id` of the input event that triggered the run.
|
|
103
|
+
* The triggering input is the one whose `event-id` matches the invocation's
|
|
104
|
+
* `inputEventId` (the last input of the originating send). The agent
|
|
105
|
+
* re-stamps it on every event it publishes for the invocation (run
|
|
106
|
+
* lifecycle + assistant outputs), mirroring `input-client-id`. This is the
|
|
107
|
+
* codec-message-id the client owns at send time, so it lets the client
|
|
108
|
+
* correlate any of those events back to the originating input without
|
|
109
|
+
* depending on a client-minted run-id or invocation-id.
|
|
110
|
+
*/
|
|
111
|
+
export const HEADER_INPUT_CODEC_MESSAGE_ID = 'input-codec-message-id';
|
|
72
112
|
|
|
73
113
|
// ---------------------------------------------------------------------------
|
|
74
|
-
//
|
|
114
|
+
// Run-end error headers (set on `ai-run-end` when `run-reason: error`)
|
|
75
115
|
// ---------------------------------------------------------------------------
|
|
76
116
|
|
|
77
|
-
/** Header:
|
|
78
|
-
export const
|
|
117
|
+
/** Header: numeric error code accompanying an `ai-run-end` with reason `error`. */
|
|
118
|
+
export const HEADER_ERROR_CODE = 'error-code';
|
|
119
|
+
|
|
120
|
+
/** Header: human-readable error message accompanying an `ai-run-end` with reason `error`. */
|
|
121
|
+
export const HEADER_ERROR_MESSAGE = 'error-message';
|
|
79
122
|
|
|
80
123
|
// ---------------------------------------------------------------------------
|
|
81
124
|
// Message / event names
|
|
82
125
|
// ---------------------------------------------------------------------------
|
|
83
126
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Message name: client->agent cancel intent. Targets a run by `run-id` (a
|
|
129
|
+
* continuation, whose run-id the client already knows) and/or by
|
|
130
|
+
* `input-codec-message-id` (a fresh send, whose run-id the agent mints at
|
|
131
|
+
* run-start — so the client can only key the cancel by the triggering input's
|
|
132
|
+
* codec-message-id it owns at send time). The agent resolves whichever is
|
|
133
|
+
* present to the registered run; a cancel that arrives before the run is known
|
|
134
|
+
* (the input-event lookup hasn't resolved the input id to a run yet) is
|
|
135
|
+
* buffered by `input-codec-message-id` and honoured when the run resolves it.
|
|
136
|
+
* Also carries an `event-id` so channel rewind redelivers it to a per-request /
|
|
137
|
+
* serverless agent that attaches after the cancel was published.
|
|
138
|
+
*/
|
|
139
|
+
export const EVENT_CANCEL = 'ai-cancel';
|
|
86
140
|
|
|
87
|
-
/** Message name: server publishes this to signal a
|
|
88
|
-
export const
|
|
141
|
+
/** Message name: server publishes this to signal a run has started. */
|
|
142
|
+
export const EVENT_RUN_START = 'ai-run-start';
|
|
89
143
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Message name: server publishes this to signal a run has suspended — paused
|
|
146
|
+
* awaiting participant input (e.g. a client tool result or approval) without
|
|
147
|
+
* ending. The run stays live and may be resumed under the same `runId`.
|
|
148
|
+
* Distinct from `ai-run-end`, which is terminal.
|
|
149
|
+
*/
|
|
150
|
+
export const EVENT_RUN_SUSPEND = 'ai-run-suspend';
|
|
92
151
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Message name: server publishes this when a subsequent invocation re-enters an
|
|
154
|
+
* already-started run (e.g. a tool-result follow-up under the same `runId`).
|
|
155
|
+
* A pure re-entry signal: unlike `ai-run-start` it carries no `parent` / `fork-of`
|
|
156
|
+
* (the original `ai-run-start` already established the run's structure).
|
|
157
|
+
*/
|
|
158
|
+
export const EVENT_RUN_RESUME = 'ai-run-resume';
|
|
95
159
|
|
|
96
|
-
/** Message name:
|
|
97
|
-
export const
|
|
160
|
+
/** Message name: server publishes this to signal a run has ended. */
|
|
161
|
+
export const EVENT_RUN_END = 'ai-run-end';
|
|
98
162
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Message name: every agent-published codec event (text, reasoning, tool calls,
|
|
165
|
+
* tool outputs, lifecycle helpers, file / source parts, data-* chunks) rides
|
|
166
|
+
* this single wire name. The codec event's own `type` is carried in the
|
|
167
|
+
* SDK-controlled codec-level `kind` header so the decoder can dispatch.
|
|
168
|
+
*/
|
|
169
|
+
export const EVENT_AI_OUTPUT = 'ai-output';
|
|
102
170
|
|
|
103
|
-
/**
|
|
104
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Message name: every client-published codec event (user-message parts,
|
|
173
|
+
* tool-approval responses, regenerate signals) rides this single wire
|
|
174
|
+
* name. The codec event's own kind is carried in the codec-level `kind`
|
|
175
|
+
* header so the decoder can dispatch.
|
|
176
|
+
*/
|
|
177
|
+
export const EVENT_AI_INPUT = 'ai-input';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps the two paths chat-js uses (see ChatClient._addAgent): the
|
|
3
|
+
* `options.agents` mutation (read by ably-js when opening the initial
|
|
4
|
+
* WebSocket) and the `params.agent` channel option (sent on ATTACH so
|
|
5
|
+
* an already-open connection still carries the identifier).
|
|
6
|
+
*
|
|
7
|
+
* `options.agents` is a private API on the Realtime client — no public
|
|
8
|
+
* typed accessor exists in the `ably` package — so this module casts to a
|
|
9
|
+
* `RealtimeWithOptions` shape to write it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type * as Ably from 'ably';
|
|
13
|
+
|
|
14
|
+
import { VERSION } from '../version.js';
|
|
15
|
+
|
|
16
|
+
interface RealtimeWithOptions extends Ably.Realtime {
|
|
17
|
+
options: { agents?: Record<string, string | undefined> };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SDK_NAME = 'ai-transport-js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Merge `agents` into `client.options.agents` and return the space-separated
|
|
24
|
+
* `params.agent` string for channel ATTACH.
|
|
25
|
+
* @param client - The Ably Realtime client to mutate.
|
|
26
|
+
* @param agents - Map of agent-name to version strings to register.
|
|
27
|
+
* @returns Channel options containing `params.agent` for `channels.get`.
|
|
28
|
+
*/
|
|
29
|
+
const injectAgents = (
|
|
30
|
+
client: Ably.Realtime,
|
|
31
|
+
// CAST: Ably.Realtime's public type omits `options.agents`, but the SDK
|
|
32
|
+
// does carry it at runtime. ably-chat-js relies on the same shape — see
|
|
33
|
+
// ChatClient._addAgent in https://github.com/ably/ably-chat-js.
|
|
34
|
+
agents: Record<string, string>,
|
|
35
|
+
): { params: { agent: string } } => {
|
|
36
|
+
const realtime = client as RealtimeWithOptions;
|
|
37
|
+
realtime.options.agents = { ...realtime.options.agents, ...agents };
|
|
38
|
+
return { params: { agent: joinAgents(agents) } };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the agent-name → version map this SDK registers for the given codec.
|
|
43
|
+
* @param codec - The codec instance whose optional identifier opts into registration.
|
|
44
|
+
* @param codec.adapterTag - The optional Ably-Agent identifier; registered as an agent when present.
|
|
45
|
+
* @returns Map of agent name to version, always including this SDK.
|
|
46
|
+
*/
|
|
47
|
+
const buildAgents = (codec?: { readonly adapterTag?: string }): Record<string, string> => {
|
|
48
|
+
const adapterTag = codec?.adapterTag;
|
|
49
|
+
const agents: Record<string, string> = { [SDK_NAME]: VERSION };
|
|
50
|
+
if (adapterTag) agents[adapterTag] = VERSION;
|
|
51
|
+
return agents;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Render an agents map as the space-separated `name/version` string Ably expects.
|
|
56
|
+
* @param agents - Map of agent name to version.
|
|
57
|
+
* @returns The space-separated `name/version` agent string.
|
|
58
|
+
*/
|
|
59
|
+
const joinAgents = (agents: Record<string, string>): string =>
|
|
60
|
+
Object.entries(agents)
|
|
61
|
+
.map(([name, version]) => `${name}/${version}`)
|
|
62
|
+
.join(' ');
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The space-separated `params.agent` string this SDK stamps on channel ATTACH —
|
|
66
|
+
* `ai-transport-js/<version>` plus the codec's adapter tag when present. Pure:
|
|
67
|
+
* unlike {@link registerAgent} it does not mutate the client. Use it to seed a
|
|
68
|
+
* `<ChannelProvider options>` so ably-js's React hooks append their own agent
|
|
69
|
+
* additively (`channelOptionsForReactHooks`) rather than overwriting this SDK's.
|
|
70
|
+
* @param codec - The codec instance whose optional identifier opts into registration.
|
|
71
|
+
* @param codec.adapterTag - The optional Ably-Agent identifier; registered as an agent when present.
|
|
72
|
+
* @returns The channel `params.agent` string.
|
|
73
|
+
*/
|
|
74
|
+
export const channelAgent = (codec?: { readonly adapterTag?: string }): string => joinAgents(buildAgents(codec));
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Register this SDK (and optionally a codec) on the supplied Realtime client
|
|
78
|
+
* and return the channel options the caller should pass to
|
|
79
|
+
* `client.channels.get(...)` so the agent is also carried on channel ATTACH.
|
|
80
|
+
* Sets `options.agents['ai-transport-js'] = VERSION`. When the codec carries an
|
|
81
|
+
* `adapterTag`, also sets `options.agents[adapterTag] = VERSION`.
|
|
82
|
+
* Idempotent — repeated calls with the same client and codec produce the same keys/values.
|
|
83
|
+
* Spec: AIT-CT1a, AIT-CT1a2, AIT-CT1a3, AIT-ST1a, AIT-ST1a2, AIT-ST1a3.
|
|
84
|
+
* @param client - The Ably Realtime client to register on.
|
|
85
|
+
* @param codec - The codec instance whose optional identifier opts into registration.
|
|
86
|
+
* @param codec.adapterTag - The optional Ably-Agent identifier; registered as an agent when present.
|
|
87
|
+
* @returns Channel options containing `params.agent` for `channels.get`.
|
|
88
|
+
*/
|
|
89
|
+
export const registerAgent = (
|
|
90
|
+
client: Ably.Realtime,
|
|
91
|
+
codec?: { readonly adapterTag?: string },
|
|
92
|
+
): { params: { agent: string } } => injectAgents(client, buildAgents(codec));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-mode resolution shared by the sessions and the React provider.
|
|
3
|
+
*
|
|
4
|
+
* Presence, pub/sub, and annotation publishing are all part of the server's
|
|
5
|
+
* default channel-mode set, so a channel attached with no mode flags is granted
|
|
6
|
+
* them automatically. LiveObjects is different: object operations require the
|
|
7
|
+
* `object_subscribe` / `object_publish` modes, which are NOT in the default
|
|
8
|
+
* set, so the channel must be attached with explicit modes to use them.
|
|
9
|
+
*
|
|
10
|
+
* Setting `modes` on the wire is a full replacement, not additive: the moment
|
|
11
|
+
* an ATTACH carries any mode flag the server takes that bitfield verbatim and
|
|
12
|
+
* never falls back to its default set. So requesting object modes means also
|
|
13
|
+
* requesting everything the default set would grant, plus the object modes.
|
|
14
|
+
* {@link AIT_BASE_MODES} is exactly the server default, so opting into extra
|
|
15
|
+
* modes adds the extras and changes nothing else.
|
|
16
|
+
*
|
|
17
|
+
* Every place that resolves the session's channel options — both session
|
|
18
|
+
* constructors and the React `<ChannelProvider>` — must funnel through
|
|
19
|
+
* {@link resolveChannelModes} so they all request the SAME modes in the SAME
|
|
20
|
+
* order. ably-js compares modes order- and duplicate-sensitively when deciding
|
|
21
|
+
* whether a `setOptions` call needs a reattach; identical arrays compare equal,
|
|
22
|
+
* so consistent resolution avoids both spurious reattaches and silent mode
|
|
23
|
+
* reversion when one writer omits modes another set.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type * as Ably from 'ably';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The modes AI Transport always needs — byte-for-byte the server's default
|
|
30
|
+
* channel-mode set (`PUBLISH | SUBSCRIBE | PRESENCE | PRESENCE_SUBSCRIBE |
|
|
31
|
+
* ANNOTATION_PUBLISH`). Because this equals the default, requesting it plus
|
|
32
|
+
* extra modes (e.g. {@link OBJECT_MODES}) grants the same access as the default
|
|
33
|
+
* set plus those extras.
|
|
34
|
+
*/
|
|
35
|
+
export const AIT_BASE_MODES: readonly Ably.ChannelMode[] = [
|
|
36
|
+
'PUBLISH',
|
|
37
|
+
'SUBSCRIBE',
|
|
38
|
+
'PRESENCE',
|
|
39
|
+
'PRESENCE_SUBSCRIBE',
|
|
40
|
+
'ANNOTATION_PUBLISH',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The channel modes required to read and write Ably LiveObjects. Pass as the
|
|
45
|
+
* session's `channelModes` option (`channelModes: OBJECT_MODES`) to enable the
|
|
46
|
+
* `object` accessor on a session and the LiveObjects channel hooks under a
|
|
47
|
+
* `<ClientSessionProvider>`.
|
|
48
|
+
*/
|
|
49
|
+
export const OBJECT_MODES: readonly Ably.ChannelMode[] = ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH'];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Canonical ordering for every known channel mode. {@link resolveChannelModes}
|
|
53
|
+
* emits modes in this order so two resolutions of the same mode set produce
|
|
54
|
+
* identical arrays, which ably-js treats as equal (no reattach churn).
|
|
55
|
+
*/
|
|
56
|
+
const MODE_ORDER: readonly Ably.ChannelMode[] = [
|
|
57
|
+
'PUBLISH',
|
|
58
|
+
'SUBSCRIBE',
|
|
59
|
+
'PRESENCE',
|
|
60
|
+
'PRESENCE_SUBSCRIBE',
|
|
61
|
+
'OBJECT_PUBLISH',
|
|
62
|
+
'OBJECT_SUBSCRIBE',
|
|
63
|
+
'ANNOTATION_PUBLISH',
|
|
64
|
+
'ANNOTATION_SUBSCRIBE',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the channel modes AI Transport should request on its channel.
|
|
69
|
+
*
|
|
70
|
+
* Returns `undefined` when the caller asks for no extra modes, so the channel
|
|
71
|
+
* attaches with no mode flags and the server applies its default set. When
|
|
72
|
+
* extra modes are supplied (e.g. {@link OBJECT_MODES}),
|
|
73
|
+
* returns {@link AIT_BASE_MODES} unioned with them, de-duplicated and in a
|
|
74
|
+
* fixed canonical order so repeated resolutions compare equal.
|
|
75
|
+
*
|
|
76
|
+
* Modes outside {@link MODE_ORDER} (which should not occur for a valid
|
|
77
|
+
* {@link Ably.ChannelMode}, but is possible with the type's lowercase aliases)
|
|
78
|
+
* are appended after the canonical modes, sorted alphabetically, so the result
|
|
79
|
+
* is still deterministic.
|
|
80
|
+
* @param extraModes - Additional modes to request on top of {@link AIT_BASE_MODES}. Omit or pass an empty array to request no modes at all.
|
|
81
|
+
* @returns The canonically-ordered, de-duplicated mode set, or `undefined` when no extra modes were requested.
|
|
82
|
+
*/
|
|
83
|
+
export const resolveChannelModes = (extraModes?: readonly Ably.ChannelMode[]): Ably.ChannelMode[] | undefined => {
|
|
84
|
+
if (extraModes === undefined || extraModes.length === 0) return undefined;
|
|
85
|
+
const requested = new Set<Ably.ChannelMode>([...AIT_BASE_MODES, ...extraModes]);
|
|
86
|
+
const ordered = MODE_ORDER.filter((mode) => requested.has(mode));
|
|
87
|
+
const unknown = [...requested].filter((mode) => !MODE_ORDER.includes(mode)).toSorted();
|
|
88
|
+
return [...ordered, ...unknown];
|
|
89
|
+
};
|