@ably/ai-transport 0.0.1 → 0.2.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 (167) hide show
  1. package/README.md +114 -116
  2. package/dist/ably-ai-transport.js +1743 -961
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +117 -39
  7. package/dist/core/agent.d.ts +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +410 -101
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +97 -17
  18. package/dist/core/transport/index.d.ts +5 -3
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -8
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +435 -0
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -402
  32. package/dist/core/transport/view.d.ts +354 -0
  33. package/dist/errors.d.ts +37 -9
  34. package/dist/index.d.ts +6 -6
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +1164 -645
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +16 -10
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +20 -11
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +23 -0
  48. package/dist/react/use-tree.d.ts +35 -0
  49. package/dist/react/use-view.d.ts +110 -0
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2748 -1625
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +50 -0
  61. package/dist/vercel/index.d.ts +4 -2
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +10298 -1410
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +70 -1
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +33 -0
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +96 -0
  68. package/dist/vercel/react/index.d.ts +4 -0
  69. package/dist/vercel/react/use-chat-transport.d.ts +66 -21
  70. package/dist/vercel/react/use-message-sync.d.ts +31 -12
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +71 -30
  73. package/dist/vercel/transport/index.d.ts +25 -18
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +47 -34
  77. package/src/constants.ts +126 -47
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +115 -58
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +438 -106
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +182 -19
  89. package/src/core/transport/index.ts +29 -22
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +58 -40
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +1167 -0
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -527
  103. package/src/core/transport/view.ts +1271 -0
  104. package/src/errors.ts +42 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +55 -39
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +27 -10
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +47 -19
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +72 -0
  116. package/src/react/use-tree.ts +84 -0
  117. package/src/react/use-view.ts +275 -0
  118. package/src/react/vite.config.ts +4 -1
  119. package/src/utils.ts +63 -45
  120. package/src/vercel/codec/decoder.ts +336 -255
  121. package/src/vercel/codec/encoder.ts +348 -196
  122. package/src/vercel/codec/events.ts +87 -0
  123. package/src/vercel/codec/index.ts +59 -14
  124. package/src/vercel/codec/reducer.ts +977 -0
  125. package/src/vercel/codec/tool-transitions.ts +122 -0
  126. package/src/vercel/index.ts +7 -3
  127. package/src/vercel/react/contexts/chat-transport-context.ts +41 -0
  128. package/src/vercel/react/contexts/chat-transport-provider.tsx +150 -0
  129. package/src/vercel/react/index.ts +13 -1
  130. package/src/vercel/react/use-chat-transport.ts +162 -42
  131. package/src/vercel/react/use-message-sync.ts +121 -22
  132. package/src/vercel/react/vite.config.ts +4 -2
  133. package/src/vercel/run-end-reason.ts +78 -0
  134. package/src/vercel/transport/chat-transport.ts +553 -113
  135. package/src/vercel/transport/index.ts +40 -28
  136. package/src/vercel/transport/run-output-stream.ts +170 -0
  137. package/src/version.ts +2 -0
  138. package/dist/core/transport/client-transport.d.ts +0 -10
  139. package/dist/core/transport/conversation-tree.d.ts +0 -9
  140. package/dist/core/transport/decode-history.d.ts +0 -41
  141. package/dist/core/transport/server-transport.d.ts +0 -7
  142. package/dist/core/transport/stream-router.d.ts +0 -19
  143. package/dist/core/transport/turn-manager.d.ts +0 -34
  144. package/dist/react/use-active-turns.d.ts +0 -8
  145. package/dist/react/use-client-transport.d.ts +0 -7
  146. package/dist/react/use-conversation-tree.d.ts +0 -20
  147. package/dist/react/use-edit.d.ts +0 -7
  148. package/dist/react/use-history.d.ts +0 -19
  149. package/dist/react/use-messages.d.ts +0 -7
  150. package/dist/react/use-regenerate.d.ts +0 -7
  151. package/dist/react/use-send.d.ts +0 -7
  152. package/dist/vercel/codec/accumulator.d.ts +0 -21
  153. package/src/core/transport/client-transport.ts +0 -959
  154. package/src/core/transport/conversation-tree.ts +0 -434
  155. package/src/core/transport/decode-history.ts +0 -337
  156. package/src/core/transport/server-transport.ts +0 -458
  157. package/src/core/transport/stream-router.ts +0 -118
  158. package/src/core/transport/turn-manager.ts +0 -147
  159. package/src/react/use-active-turns.ts +0 -61
  160. package/src/react/use-client-transport.ts +0 -37
  161. package/src/react/use-conversation-tree.ts +0 -71
  162. package/src/react/use-edit.ts +0 -24
  163. package/src/react/use-history.ts +0 -111
  164. package/src/react/use-messages.ts +0 -32
  165. package/src/react/use-regenerate.ts +0 -24
  166. package/src/react/use-send.ts +0 -25
  167. package/src/vercel/codec/accumulator.ts +0 -603
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared wire decode-and-apply engine.
3
+ *
4
+ * The client's live decode loop and the View's history replay both reconstruct
5
+ * the conversation Tree from the same raw Ably wire log. This module is the one
6
+ * place that classifies a wire message (run-lifecycle vs codec-decoded), parses
7
+ * or decodes it, and applies it to the Tree — so the two paths can never drift.
8
+ */
9
+
10
+ import type * as Ably from 'ably';
11
+
12
+ import { HEADER_RUN_ID } from '../../constants.js';
13
+ import { getTransportHeaders } from '../../utils.js';
14
+ import type { Codec, CodecInputEvent, CodecOutputEvent, Decoder } from '../codec/types.js';
15
+ import { isRunLifecycleName, parseRunLifecycle } from './headers.js';
16
+ import type { TreeInternal } from './tree.js';
17
+ import type { RunLifecycleEvent } from './types.js';
18
+
19
+ /**
20
+ * Apply one inbound wire message to the tree.
21
+ *
22
+ * Run-lifecycle messages are turned into a {@link RunLifecycleEvent} via
23
+ * {@link parseRunLifecycle} and applied with `applyRunLifecycle`; everything
24
+ * else is decoded with `decoder` and applied with `applyMessage`, skipping
25
+ * wire-only carriers that decode to no events and carry no run-id (the eventual
26
+ * reply run is created later by its run-start).
27
+ *
28
+ * Does NOT emit the tree's `ably-message` event — the caller owns that, because
29
+ * the live loop emits per message while history replay emits in a batch once
30
+ * the whole page is applied. Returns the parsed lifecycle event so a live
31
+ * caller can run its own side-effects (resolving a pending run-start,
32
+ * surfacing an agent error); returns `undefined` for a codec-decoded message
33
+ * or a lifecycle message that carried no run-id.
34
+ * @param tree - The tree to apply the message to.
35
+ * @param decoder - The codec decoder used for non-lifecycle messages.
36
+ * @param rawMsg - The inbound Ably wire message.
37
+ * @returns The parsed run-lifecycle event, or `undefined`.
38
+ */
39
+ export const applyWireMessage = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(
40
+ tree: TreeInternal<TInput, TOutput, TProjection>,
41
+ decoder: Decoder<TInput, TOutput>,
42
+ rawMsg: Ably.InboundMessage,
43
+ ): RunLifecycleEvent | undefined => {
44
+ const headers = getTransportHeaders(rawMsg);
45
+ const serial = rawMsg.serial;
46
+
47
+ if (isRunLifecycleName(rawMsg.name)) {
48
+ const event = parseRunLifecycle(rawMsg.name, headers, serial);
49
+ if (event) tree.applyRunLifecycle(event);
50
+ return event;
51
+ }
52
+
53
+ const { inputs, outputs } = decoder.decode(rawMsg);
54
+ if (inputs.length > 0 || outputs.length > 0 || headers[HEADER_RUN_ID]) {
55
+ tree.applyMessage({ inputs, outputs }, headers, serial);
56
+ }
57
+ return undefined;
58
+ };
59
+
60
+ /**
61
+ * Decode one wire message with `decoder` and fold its events into `projection`,
62
+ * returning the updated projection. Unlike {@link applyWireMessage} this builds
63
+ * a standalone projection rather than applying to a tree — used by the agent's
64
+ * conversation reconstruction. The caller owns the decoder so its streaming
65
+ * state can span the messages of a run, and chooses the reducer routing key.
66
+ * @param codec - The codec whose inherited Reducer `fold` method folds each decoded event into the projection.
67
+ * @param decoder - The caller-owned codec decoder (reused across a run's wires).
68
+ * @param projection - The projection to fold the message's events into.
69
+ * @param rawMsg - The wire message to decode and fold.
70
+ * @param messageId - The reducer routing key (codec-message-id) for this message.
71
+ * @returns The projection after folding all of the message's decoded events.
72
+ */
73
+ export const foldMessageInto = <
74
+ TInput extends CodecInputEvent,
75
+ TOutput extends CodecOutputEvent,
76
+ TProjection,
77
+ TMessage,
78
+ >(
79
+ codec: Codec<TInput, TOutput, TProjection, TMessage>,
80
+ decoder: Decoder<TInput, TOutput>,
81
+ projection: TProjection,
82
+ rawMsg: Ably.InboundMessage,
83
+ messageId: string,
84
+ ): TProjection => {
85
+ const { inputs, outputs } = decoder.decode(rawMsg);
86
+ let next = projection;
87
+ for (const event of [...inputs, ...outputs]) {
88
+ next = codec.fold(next, event, { serial: rawMsg.serial ?? '', messageId });
89
+ }
90
+ return next;
91
+ };
@@ -1,46 +1,209 @@
1
1
  /**
2
2
  * Transport header builder.
3
3
  *
4
- * Single source of truth for which `x-ably-*` headers every transport
5
- * message carries. Used by the server transport (addMessages, streamResponse)
6
- * and will be used by the client transport (optimistic message stamping).
4
+ * Single source of truth for which transport headers every transport
5
+ * message carries. Used by the agent session (pipe, addEvents) and by
6
+ * the client session (optimistic message stamping).
7
7
  */
8
8
 
9
9
  import {
10
+ EVENT_RUN_END,
11
+ EVENT_RUN_RESUME,
12
+ EVENT_RUN_START,
13
+ EVENT_RUN_SUSPEND,
14
+ HEADER_CODEC_MESSAGE_ID,
15
+ HEADER_EVENT_ID,
10
16
  HEADER_FORK_OF,
11
- HEADER_MSG_ID,
17
+ HEADER_INPUT_CLIENT_ID,
18
+ HEADER_INPUT_CODEC_MESSAGE_ID,
19
+ HEADER_INVOCATION_ID,
20
+ HEADER_MSG_REGENERATE,
12
21
  HEADER_PARENT,
13
22
  HEADER_ROLE,
14
- HEADER_TURN_CLIENT_ID,
15
- HEADER_TURN_ID,
23
+ HEADER_RUN_CLIENT_ID,
24
+ HEADER_RUN_ID,
25
+ HEADER_RUN_REASON,
16
26
  } from '../../constants.js';
27
+ import type { RunEndReason, RunLifecycleEvent } from './types.js';
17
28
 
18
29
  /**
19
30
  * Build the standard transport header set for a message.
20
31
  * @param opts - The header values to include.
21
32
  * @param opts.role - Message role (e.g. "user", "assistant").
22
- * @param opts.turnId - Turn correlation ID.
23
- * @param opts.msgId - Message identity.
24
- * @param opts.turnClientId - ClientId of the turn initiator.
25
- * @param opts.parent - Preceding message's msg-id (for branching). Null means root.
26
- * @param opts.forkOf - Forked message's msg-id (for edit/regen).
27
- * @returns A headers record with the `x-ably-*` transport headers set.
33
+ * @param opts.runId - Run correlation ID, or `undefined` for a fresh client
34
+ * input (the agent mints run-ids, so it is not known synchronously). Omitted
35
+ * from the headers when undefined; a continuation still carries the known run-id.
36
+ * @param opts.codecMessageId - Message identity — the wire `codec-message-id` for this message.
37
+ * @param opts.runClientId - ClientId of the run initiator.
38
+ * @param opts.parent - Preceding message's codec-message-id (for branching).
39
+ * @param opts.forkOf - Forked user-prompt's codec-message-id (for edits — creates a Run-level fork sibling).
40
+ * @param opts.regenerates - Assistant codec-message-id this run regenerates. Stamps
41
+ * `msg-regenerate`. Distinct from `forkOf`: regenerate is a
42
+ * continuation of the prior run (no Run-level fork), with the message
43
+ * replacement resolved at projection extraction time.
44
+ * @param opts.invocationId - Agent-minted invocation id. Stamped by the agent on every event it publishes for the invocation (run lifecycle + outputs) so the client can observe it; not set by the client on the input.
45
+ * @param opts.inputClientId - ClientId of the input event (the `ai-input`) that
46
+ * drove the current invocation. The agent reads it from the publisher's
47
+ * Ably-level `clientId` on the matched input event and re-stamps it on its
48
+ * own publishes (run lifecycle + outputs). Differs from `runClientId` on
49
+ * continuation invocations driven by an input from a non-owner.
50
+ * @param opts.inputEventId - Per-event identifier. Set on each client-published user-prompt message; the invocation body's `inputEventIds` lists the ids the agent should look up.
51
+ * @param opts.inputCodecMessageId - The codec-message-id of the input event that
52
+ * triggered the current invocation (the one whose `event-id` matched the
53
+ * invocation's `inputEventId`). The agent re-stamps it on every event it
54
+ * publishes for the invocation (run lifecycle + outputs), mirroring
55
+ * `inputClientId`, so the client can correlate any of those events back to
56
+ * the originating input by the id it owned at send time.
57
+ * @returns A headers record with the transport headers set.
28
58
  */
29
59
  export const buildTransportHeaders = (opts: {
30
60
  role: string;
31
- turnId: string;
32
- msgId: string;
33
- turnClientId?: string;
34
- parent?: string | null;
61
+ runId?: string;
62
+ codecMessageId: string;
63
+ runClientId?: string;
64
+ parent?: string;
35
65
  forkOf?: string;
66
+ regenerates?: string;
67
+ invocationId?: string;
68
+ inputClientId?: string;
69
+ inputCodecMessageId?: string;
70
+ inputEventId?: string;
36
71
  }): Record<string, string> => {
37
72
  const h: Record<string, string> = {
38
73
  [HEADER_ROLE]: opts.role,
39
- [HEADER_TURN_ID]: opts.turnId,
40
- [HEADER_MSG_ID]: opts.msgId,
74
+ [HEADER_CODEC_MESSAGE_ID]: opts.codecMessageId,
41
75
  };
42
- if (opts.turnClientId !== undefined) h[HEADER_TURN_CLIENT_ID] = opts.turnClientId;
76
+ if (opts.runId !== undefined) h[HEADER_RUN_ID] = opts.runId;
77
+ if (opts.runClientId !== undefined) h[HEADER_RUN_CLIENT_ID] = opts.runClientId;
43
78
  if (opts.parent) h[HEADER_PARENT] = opts.parent;
44
79
  if (opts.forkOf) h[HEADER_FORK_OF] = opts.forkOf;
80
+ if (opts.regenerates) h[HEADER_MSG_REGENERATE] = opts.regenerates;
81
+ if (opts.invocationId) h[HEADER_INVOCATION_ID] = opts.invocationId;
82
+ if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;
83
+ if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;
84
+ if (opts.inputEventId) h[HEADER_EVENT_ID] = opts.inputEventId;
45
85
  return h;
46
86
  };
87
+
88
+ /**
89
+ * Build the transport header set for a run-lifecycle event (run-start,
90
+ * run-resume, run-suspend, run-end). Single source of truth for lifecycle
91
+ * header stamping, mirroring {@link buildTransportHeaders} for the
92
+ * message-carrier path. Every field except `runId`/`runClientId` is optional
93
+ * and omitted when not provided.
94
+ *
95
+ * A resume suppresses the structural `parent` / `forkOf` / `regenerates`
96
+ * headers — the caller passes them only for a fresh run-start. `reason` is
97
+ * stamped only on run-end.
98
+ * @param opts - The lifecycle header values to include.
99
+ * @param opts.runId - The run's id.
100
+ * @param opts.runClientId - ClientId of the run initiator (empty string when unknown).
101
+ * @param opts.parent - Structural parent codec-message-id (fresh run-start only).
102
+ * @param opts.forkOf - Forked user-prompt codec-message-id (fresh run-start only).
103
+ * @param opts.regenerates - Regenerated assistant codec-message-id (fresh run-start only).
104
+ * @param opts.invocationId - Agent-minted invocation id; carried on every lifecycle event.
105
+ * @param opts.inputClientId - ClientId of the triggering input event.
106
+ * @param opts.inputCodecMessageId - Codec-message-id of the triggering input event.
107
+ * @param opts.reason - Terminal reason; stamped on run-end only.
108
+ * @returns A headers record with the lifecycle headers set.
109
+ */
110
+ export const buildLifecycleHeaders = (opts: {
111
+ runId: string;
112
+ runClientId: string;
113
+ parent?: string;
114
+ forkOf?: string;
115
+ regenerates?: string;
116
+ invocationId?: string;
117
+ inputClientId?: string;
118
+ inputCodecMessageId?: string;
119
+ reason?: RunEndReason;
120
+ }): Record<string, string> => {
121
+ const h: Record<string, string> = {
122
+ [HEADER_RUN_ID]: opts.runId,
123
+ [HEADER_RUN_CLIENT_ID]: opts.runClientId,
124
+ };
125
+ if (opts.reason !== undefined) h[HEADER_RUN_REASON] = opts.reason;
126
+ if (opts.parent !== undefined) h[HEADER_PARENT] = opts.parent;
127
+ if (opts.forkOf !== undefined) h[HEADER_FORK_OF] = opts.forkOf;
128
+ if (opts.regenerates !== undefined) h[HEADER_MSG_REGENERATE] = opts.regenerates;
129
+ if (opts.invocationId !== undefined) h[HEADER_INVOCATION_ID] = opts.invocationId;
130
+ if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;
131
+ if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;
132
+ return h;
133
+ };
134
+
135
+ /** The four run-lifecycle Ably message names. */
136
+ type RunLifecycleName =
137
+ | typeof EVENT_RUN_START
138
+ | typeof EVENT_RUN_SUSPEND
139
+ | typeof EVENT_RUN_RESUME
140
+ | typeof EVENT_RUN_END;
141
+
142
+ /**
143
+ * Whether an Ably message `name` is one of the run-lifecycle event names
144
+ * (run-start / run-suspend / run-resume / run-end). Single source of truth for
145
+ * the classification both decode loops and the agent's history scan use to
146
+ * route lifecycle wires away from the codec decoder. Narrows `name` to a
147
+ * lifecycle name so callers can pass it straight to {@link parseRunLifecycle}.
148
+ * @param name - The inbound Ably message `name`, or undefined.
149
+ * @returns True when `name` is a run-lifecycle event name.
150
+ */
151
+ export const isRunLifecycleName = (name: string | undefined): name is RunLifecycleName =>
152
+ name === EVENT_RUN_START || name === EVENT_RUN_SUSPEND || name === EVENT_RUN_RESUME || name === EVENT_RUN_END;
153
+
154
+ /**
155
+ * Parse an inbound run-lifecycle Ably message into a {@link RunLifecycleEvent}.
156
+ *
157
+ * Single source of truth for turning the wire run-lifecycle message `name`,
158
+ * transport headers, and channel serial into the structured lifecycle event
159
+ * the Tree consumes. Used by the client decode loop (live) and the View's
160
+ * history replay so both build the event identically.
161
+ * @param name - The inbound Ably message `name`.
162
+ * @param headers - Transport headers from the inbound Ably message.
163
+ * @param serial - Ably channel serial of the message, or `undefined` for an
164
+ * optimistic local event. Stamped onto the returned event.
165
+ * @returns The lifecycle event, or `undefined` when `name` is not a
166
+ * run-lifecycle event name or the message carries no `run-id`.
167
+ */
168
+ export const parseRunLifecycle = (
169
+ name: string,
170
+ headers: Record<string, string>,
171
+ serial: string | undefined,
172
+ ): RunLifecycleEvent | undefined => {
173
+ const runId = headers[HEADER_RUN_ID];
174
+ if (!runId) return undefined;
175
+
176
+ const clientId = headers[HEADER_RUN_CLIENT_ID] ?? '';
177
+
178
+ if (name === EVENT_RUN_START) {
179
+ const parent = headers[HEADER_PARENT];
180
+ const forkOf = headers[HEADER_FORK_OF];
181
+ const regenerates = headers[HEADER_MSG_REGENERATE];
182
+ return {
183
+ type: 'start',
184
+ runId,
185
+ clientId,
186
+ serial,
187
+ invocationId: headers[HEADER_INVOCATION_ID] ?? '',
188
+ ...(parent !== undefined && { parent }),
189
+ ...(forkOf !== undefined && { forkOf }),
190
+ ...(regenerates !== undefined && { regenerates }),
191
+ };
192
+ }
193
+
194
+ if (name === EVENT_RUN_SUSPEND) {
195
+ return { type: 'suspend', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '' };
196
+ }
197
+
198
+ if (name === EVENT_RUN_RESUME) {
199
+ return { type: 'resume', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '' };
200
+ }
201
+
202
+ if (name === EVENT_RUN_END) {
203
+ // CAST: agent always writes a valid RunEndReason; default to 'complete' for robustness.
204
+ const reason = (headers[HEADER_RUN_REASON] ?? 'complete') as RunEndReason;
205
+ return { type: 'end', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '', reason };
206
+ }
207
+
208
+ return undefined;
209
+ };
@@ -1,34 +1,41 @@
1
1
  // Shared types
2
2
  export type {
3
- ActiveTurn,
4
- AddMessageOptions,
5
- AddMessagesResult,
6
- CancelFilter,
3
+ ActiveRun,
4
+ AgentSession,
5
+ AgentSessionOptions,
6
+ BranchSelection,
7
7
  CancelRequest,
8
- ClientTransport,
9
- ClientTransportOptions,
10
- CloseOptions,
8
+ ClientSession,
9
+ ClientSessionOptions,
11
10
  ConversationNode,
12
- ConversationTree,
13
- LoadHistoryOptions,
14
- MessageWithHeaders,
15
- NewTurnOptions,
16
- PaginatedMessages,
11
+ EventsNode,
12
+ InputNode,
13
+ LoadConversationOptions,
14
+ MessageNode,
15
+ OutputEvent,
16
+ PipeOptions,
17
+ Run,
18
+ RunEndReason,
19
+ RunInfo,
20
+ RunLifecycleEvent,
21
+ RunNode,
22
+ RunRuntime,
23
+ RunView,
17
24
  SendOptions,
18
- ServerTransport,
19
- ServerTransportOptions,
20
- StreamResponseOptions,
21
25
  StreamResult,
22
- Turn,
23
- TurnEndReason,
24
- TurnLifecycleEvent,
26
+ Tree,
27
+ View,
25
28
  } from './types.js';
26
29
 
27
- // Server transport
28
- export { createServerTransport } from './server-transport.js';
30
+ // Invocation
31
+ export type { InvocationData } from './invocation.js';
32
+ export { Invocation } from './invocation.js';
29
33
 
30
- // Client transport
31
- export { createClientTransport } from './client-transport.js';
34
+ // Agent session
35
+ export { createAgentSession } from './agent-session.js';
36
+
37
+ // Client session
38
+ export { createClientSession } from './client-session.js';
32
39
 
33
40
  // Header builder
34
41
  export { buildTransportHeaders } from './headers.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Helpers for FIFO-bounded `Map`s — Maps used as capacity-limited buffers that
3
+ * must evict their oldest entry when full.
4
+ */
5
+
6
+ /**
7
+ * Make room for a new key in a FIFO-bounded map: if `map` is at (or over)
8
+ * `limit` and does not already contain `key`, evict the oldest entry (insertion
9
+ * order) and return its key so the caller can log the eviction. Returns
10
+ * `undefined` when nothing was evicted (the key already exists, the map is
11
+ * below the limit, or it is empty).
12
+ *
13
+ * The caller performs the actual set/append afterwards — this only frees a
14
+ * slot — so it works for maps whose values are replaced and for maps whose
15
+ * values are appended-to lists.
16
+ * @param map - The bounded map to evict from.
17
+ * @param key - The key about to be added; an existing key never evicts.
18
+ * @param limit - The maximum number of entries the map may hold.
19
+ * @returns The evicted key, or `undefined` if nothing was evicted.
20
+ */
21
+ export const evictOldestIfFull = <K, V>(map: Map<K, V>, key: K, limit: number): K | undefined => {
22
+ if (map.has(key) || map.size < limit) return undefined;
23
+ const oldest = map.keys().next().value;
24
+ if (oldest === undefined) return undefined;
25
+ map.delete(oldest);
26
+ return oldest;
27
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Invocation — value object wrapping the JSON body that a client sends to
3
+ * an agent's HTTP endpoint to start a run.
4
+ *
5
+ * The data shape is the wire contract; the {@link Invocation} class is a
6
+ * runtime view of that data with the same fields. {@link Invocation.fromJSON}
7
+ * is the entry point used by agent handlers:
8
+ *
9
+ * ```ts
10
+ * const data = (await req.json()) as InvocationData;
11
+ * const invocation = Invocation.fromJSON(data);
12
+ * const run = session.createRun(invocation, { signal: req.signal });
13
+ * await run.start();
14
+ * await run.loadProjection(); // fetch run projection from the channel
15
+ * const messages = run.messages;
16
+ * ```
17
+ *
18
+ * The body carries only what the agent needs out-of-band before the channel
19
+ * is observable: the session/channel name and the `inputEventId` that triggered
20
+ * the invocation. The agent mints the `invocationId` itself (one per HTTP
21
+ * request) and returns it on the HTTP response, so it is not a body field. Run
22
+ * identity also lives on the channel: the agent mints the `runId` for a fresh
23
+ * run and reads the existing `runId` off the triggering input event for a
24
+ * continuation — so the body carries no run-id either. Per-message metadata —
25
+ * `clientId`, `parent`, `forkOf`, continuation status — likewise lives on the
26
+ * channel and is resolved by the agent from the triggering input event, not
27
+ * from the body. The `inputClientId` the agent re-stamps on its own publishes
28
+ * comes from the publisher's Ably `clientId` on the matched input event, not
29
+ * from a body field.
30
+ */
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Wire shape
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Wire shape of a single invocation — the JSON body sent from the client
38
+ * transport's HTTP POST to the agent endpoint.
39
+ */
40
+ export interface InvocationData {
41
+ /**
42
+ * Identifier for the specific input event on the channel that triggered
43
+ * this invocation. The agent locates the event via the `event-id`
44
+ * header. Its wire headers carry the run-id for a continuation (absent for
45
+ * a fresh run), so run identity is resolved from the channel, not the body.
46
+ */
47
+ inputEventId: string;
48
+ /** Logical name of the session (chat) — used as the Ably channel name. */
49
+ sessionName: string;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Runtime view
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Runtime view of an {@link InvocationData}. Constructed via
58
+ * {@link Invocation.fromJSON}. Read-only; carries no behaviour beyond
59
+ * exposing its fields.
60
+ */
61
+ // Spec: AIT-ST13
62
+ export class Invocation {
63
+ /**
64
+ * Identifier for the specific input event on the channel that triggered
65
+ * this invocation. Run identity is resolved from that event's wire headers
66
+ * (or minted), not from the body.
67
+ */
68
+ readonly inputEventId: string;
69
+ /** Logical name of the session (chat). Used as the Ably channel name. */
70
+ readonly sessionName: string;
71
+
72
+ private constructor(data: InvocationData) {
73
+ this.inputEventId = data.inputEventId;
74
+ this.sessionName = data.sessionName;
75
+ }
76
+
77
+ /**
78
+ * Build an Invocation from its JSON wire shape.
79
+ * @param data - Parsed JSON body matching {@link InvocationData}.
80
+ * @returns A new Invocation exposing the same fields.
81
+ */
82
+ static fromJSON(data: InvocationData): Invocation {
83
+ return new Invocation(data);
84
+ }
85
+
86
+ /**
87
+ * Serialise this invocation to its JSON wire shape — the body a client
88
+ * POSTs to the agent's endpoint to wake a run. Round-trips through
89
+ * {@link Invocation.fromJSON}.
90
+ * @returns The {@link InvocationData} carrying this invocation's identity.
91
+ */
92
+ toJSON(): InvocationData {
93
+ return {
94
+ inputEventId: this.inputEventId,
95
+ sessionName: this.sessionName,
96
+ };
97
+ }
98
+ }