@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
package/src/react/use-view.ts
CHANGED
|
@@ -1,32 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useView — reactive paginated view of the conversation.
|
|
3
3
|
*
|
|
4
|
-
* Subscribes to view updates and exposes the visible
|
|
5
|
-
* write operations, pagination state, and a `loadOlder`
|
|
6
|
-
* to use a
|
|
7
|
-
* {@link View} directly. When both are omitted,
|
|
8
|
-
* {@link
|
|
4
|
+
* Subscribes to view updates and exposes the visible messages, msg-anchored
|
|
5
|
+
* branch navigation, write operations, pagination state, and a `loadOlder`
|
|
6
|
+
* callback. Pass `session` to use a session's default view, or `view` to
|
|
7
|
+
* subscribe to a specific {@link View} directly. When both are omitted,
|
|
8
|
+
* defaults to the nearest {@link ClientSessionProvider}'s session via context.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as Ably from 'ably';
|
|
12
|
-
import { useCallback,
|
|
12
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
13
13
|
|
|
14
|
-
import type {
|
|
14
|
+
import type { CodecInputEvent, CodecMessage, CodecOutputEvent } from '../core/codec/types.js';
|
|
15
|
+
import type { ActiveRun, BranchSelection, RunInfo, SendOptions, View } from '../core/transport/types.js';
|
|
15
16
|
import { ErrorCode } from '../errors.js';
|
|
16
|
-
import {
|
|
17
|
+
import type { BaseSessionOption } from './internal/use-resolved-session.js';
|
|
18
|
+
import { useResolvedSession } from './internal/use-resolved-session.js';
|
|
17
19
|
|
|
18
|
-
/** Options for
|
|
19
|
-
export interface UseViewOptions
|
|
20
|
-
|
|
20
|
+
/** Options for {@link useView}. */
|
|
21
|
+
export interface UseViewOptions<
|
|
22
|
+
TInput extends CodecInputEvent,
|
|
23
|
+
TOutput extends CodecOutputEvent,
|
|
24
|
+
TProjection,
|
|
25
|
+
TMessage,
|
|
26
|
+
> extends BaseSessionOption<TInput, TOutput, TProjection, TMessage> {
|
|
27
|
+
/** A specific {@link View} to subscribe to directly. Takes priority over `session`. */
|
|
28
|
+
view?: View<TInput, TMessage> | null;
|
|
29
|
+
/** Number of older codecMessages to reveal per page (exactly `limit`, fewer only at the end of history). When provided, auto-loads the first page on mount. */
|
|
21
30
|
limit?: number;
|
|
31
|
+
/** When `true`, skip all subscriptions and return an empty handle immediately. */
|
|
32
|
+
skip?: boolean;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
/** Handle for the paginated, branch-aware conversation view. */
|
|
25
|
-
export interface ViewHandle<
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
export interface ViewHandle<TInput extends CodecInputEvent, TMessage> {
|
|
37
|
+
/**
|
|
38
|
+
* The visible messages along the selected branch, concatenated across all
|
|
39
|
+
* visible Runs, each paired with its codec-message-id (see
|
|
40
|
+
* {@link CodecMessage}). Read the domain object from each entry's
|
|
41
|
+
* `message` field.
|
|
42
|
+
*
|
|
43
|
+
* Correlate a rendered message back to the View — `runOf`,
|
|
44
|
+
* `branchSelection`, `selectSibling`, `regenerate`, or `edit` — via its
|
|
45
|
+
* `codecMessageId`, which the SDK assigns and tracks independently of any
|
|
46
|
+
* identity the domain `message` may carry. See {@link View.getMessages}.
|
|
47
|
+
*/
|
|
48
|
+
messages: CodecMessage<TMessage>[];
|
|
30
49
|
/** Whether there are older messages that can be revealed via `loadOlder`. */
|
|
31
50
|
hasOlder: boolean;
|
|
32
51
|
/** Whether a page load is currently in progress. */
|
|
@@ -39,65 +58,90 @@ export interface ViewHandle<TEvent, TMessage> {
|
|
|
39
58
|
loadError: Ably.ErrorInfo | undefined;
|
|
40
59
|
/**
|
|
41
60
|
* Load older messages into the view. No-op if already loading.
|
|
42
|
-
* On failure, `
|
|
61
|
+
* On failure, `loadError` is set; on success, `loadError` is cleared.
|
|
43
62
|
*/
|
|
44
63
|
loadOlder: () => Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Look up the {@link RunInfo} for the Run that owns `codecMessageId`.
|
|
66
|
+
* Returns `undefined` when the codec-message-id hasn't been observed.
|
|
67
|
+
* See {@link View.runOf}.
|
|
68
|
+
*/
|
|
69
|
+
runOf: (codecMessageId: string) => RunInfo | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Direct lookup by runId. Returns `undefined` when the Run hasn't been
|
|
72
|
+
* observed. See {@link View.run}.
|
|
73
|
+
*/
|
|
74
|
+
run: (runId: string) => RunInfo | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Snapshot of the visible Runs along the selected branch, in
|
|
77
|
+
* chronological order. Returns `[]` when the view isn't resolved.
|
|
78
|
+
* See {@link View.runs}.
|
|
79
|
+
*/
|
|
80
|
+
runs: () => RunInfo[];
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the {@link BranchSelection} bundle anchored at
|
|
83
|
+
* `codecMessageId`. Always returns a safe object — see
|
|
84
|
+
* {@link BranchSelection}. See {@link View.branchSelection}.
|
|
85
|
+
*/
|
|
86
|
+
branchSelection: (codecMessageId: string) => BranchSelection<TMessage>;
|
|
87
|
+
/**
|
|
88
|
+
* Select a sibling at the branch point anchored at `codecMessageId`.
|
|
89
|
+
* `index` is clamped to `[0, siblings.length - 1]`. Silent no-op when
|
|
90
|
+
* `codecMessageId` isn't a branch anchor. See {@link View.selectSibling}.
|
|
91
|
+
*/
|
|
92
|
+
selectSibling: (codecMessageId: string, index: number) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Send one or more TInputs on the channel and fire a POST. See {@link View.send}.
|
|
95
|
+
* @throws Ably.ErrorInfo with code {@link ErrorCode.InvalidArgument} when no view is resolved (before the session is available, or when `skip` is `true`).
|
|
96
|
+
*/
|
|
97
|
+
send: (events: TInput | TInput[], options?: SendOptions) => Promise<ActiveRun>;
|
|
98
|
+
/**
|
|
99
|
+
* Regenerate an assistant message, using this view's branch for history.
|
|
100
|
+
* @throws Ably.ErrorInfo with code {@link ErrorCode.InvalidArgument} when no view is resolved (before the session is available, or when `skip` is `true`).
|
|
101
|
+
*/
|
|
102
|
+
regenerate: (messageId: string, options?: SendOptions) => Promise<ActiveRun>;
|
|
103
|
+
/**
|
|
104
|
+
* Edit a user message, forking from this view's branch.
|
|
105
|
+
* Rejects with an `Ably.ErrorInfo` (code {@link ErrorCode.InvalidArgument}) if no view is resolved — e.g. before the session is available, or when `skip` is `true`.
|
|
106
|
+
*/
|
|
107
|
+
edit: (messageId: string, inputs: TInput | TInput[], options?: SendOptions) => Promise<ActiveRun>;
|
|
63
108
|
}
|
|
64
109
|
|
|
65
110
|
/**
|
|
66
|
-
*
|
|
111
|
+
* Fallback returned by `branchSelection` when the view isn't resolved.
|
|
112
|
+
* Same shape the view returns for an unknown codec-message-id, so callers
|
|
113
|
+
* can destructure uniformly.
|
|
114
|
+
*/
|
|
115
|
+
const EMPTY_BRANCH_SELECTION: BranchSelection<never> = {
|
|
116
|
+
hasSiblings: false,
|
|
117
|
+
siblings: [],
|
|
118
|
+
index: 0,
|
|
119
|
+
selected: undefined,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Subscribe to a view and return the visible messages with pagination, navigation, and write operations.
|
|
67
124
|
*
|
|
68
|
-
* `view` takes priority over `
|
|
69
|
-
* {@link
|
|
125
|
+
* `view` takes priority over `session`. When neither is provided, the nearest
|
|
126
|
+
* {@link ClientSessionProvider}'s session is used. When `limit` is provided, auto-loads
|
|
70
127
|
* the first page on mount (SWR-style).
|
|
71
128
|
* @param props - Options for selecting the view source and configuring auto-load.
|
|
72
|
-
* @param props.
|
|
73
|
-
* @param props.view - A specific {@link View} to subscribe to directly. Takes priority over `
|
|
74
|
-
* @param props.limit -
|
|
129
|
+
* @param props.session - Client session whose default view to subscribe to; defaults to the nearest provider.
|
|
130
|
+
* @param props.view - A specific {@link View} to subscribe to directly. Takes priority over `session`.
|
|
131
|
+
* @param props.limit - Number of older codecMessages to reveal per page (exactly `limit`, fewer only at end of history); when provided, auto-loads the first page on mount.
|
|
75
132
|
* @param props.skip - When `true`, skip all subscriptions and return an empty handle.
|
|
76
|
-
* @returns A {@link ViewHandle} with
|
|
133
|
+
* @returns A {@link ViewHandle} with messages, pagination state, navigation, write operations, and loadOlder.
|
|
77
134
|
*/
|
|
78
|
-
export const useView = <
|
|
79
|
-
|
|
135
|
+
export const useView = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>({
|
|
136
|
+
session,
|
|
80
137
|
view,
|
|
81
138
|
limit,
|
|
82
139
|
skip,
|
|
83
|
-
}: {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/** When provided, auto-loads the first page on mount. Omit for manual loading. */
|
|
89
|
-
limit?: number;
|
|
90
|
-
/** When `true`, skip all subscriptions and return an empty handle immediately. */
|
|
91
|
-
skip?: boolean;
|
|
92
|
-
} = {}): ViewHandle<TEvent, TMessage> => {
|
|
93
|
-
const nearestSlot = useContext(NearestTransportContext);
|
|
94
|
-
// CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.
|
|
95
|
-
const resolvedTransport = skip
|
|
96
|
-
? undefined
|
|
97
|
-
: (transport ?? (nearestSlot?.transport as unknown as ClientTransport<TEvent, TMessage> | undefined));
|
|
98
|
-
const resolvedView = skip ? undefined : (view ?? resolvedTransport?.view);
|
|
99
|
-
|
|
100
|
-
const [nodes, setNodes] = useState<MessageNode<TMessage>[]>(() => resolvedView?.flattenNodes() ?? []);
|
|
140
|
+
}: UseViewOptions<TInput, TOutput, TProjection, TMessage> = {}): ViewHandle<TInput, TMessage> => {
|
|
141
|
+
const resolvedSession = useResolvedSession({ session, skip });
|
|
142
|
+
const resolvedView = skip ? undefined : (view ?? resolvedSession?.view);
|
|
143
|
+
|
|
144
|
+
const [messages, setMessages] = useState<CodecMessage<TMessage>[]>(() => resolvedView?.getMessages() ?? []);
|
|
101
145
|
const [hasOlder, setHasOlder] = useState(() => resolvedView?.hasOlder() ?? false);
|
|
102
146
|
const [loading, setLoading] = useState(false);
|
|
103
147
|
const [loadError, setLoadError] = useState<Ably.ErrorInfo | undefined>();
|
|
@@ -112,7 +156,7 @@ export const useView = <TEvent, TMessage>({
|
|
|
112
156
|
// Subscribe to view updates
|
|
113
157
|
useEffect(() => {
|
|
114
158
|
if (!resolvedView) {
|
|
115
|
-
|
|
159
|
+
setMessages([]);
|
|
116
160
|
setHasOlder(false);
|
|
117
161
|
setLoadError(undefined);
|
|
118
162
|
return;
|
|
@@ -122,12 +166,12 @@ export const useView = <TEvent, TMessage>({
|
|
|
122
166
|
autoLoadedRef.current = false;
|
|
123
167
|
|
|
124
168
|
// Sync initial state
|
|
125
|
-
|
|
169
|
+
setMessages(resolvedView.getMessages());
|
|
126
170
|
setHasOlder(resolvedView.hasOlder());
|
|
127
171
|
setLoadError(undefined);
|
|
128
172
|
|
|
129
173
|
const unsub = resolvedView.on('update', () => {
|
|
130
|
-
|
|
174
|
+
setMessages(resolvedView.getMessages());
|
|
131
175
|
setHasOlder(resolvedView.hasOlder());
|
|
132
176
|
});
|
|
133
177
|
return unsub;
|
|
@@ -158,30 +202,39 @@ export const useView = <TEvent, TMessage>({
|
|
|
158
202
|
void loadOlder();
|
|
159
203
|
}, [autoLoad, resolvedView, loadOlder]);
|
|
160
204
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const select = useCallback(
|
|
165
|
-
(msgId: string, index: number) => {
|
|
166
|
-
resolvedView?.select(msgId, index);
|
|
167
|
-
},
|
|
205
|
+
// Run lookups
|
|
206
|
+
const runOf = useCallback(
|
|
207
|
+
(codecMessageId: string): RunInfo | undefined => resolvedView?.runOf(codecMessageId),
|
|
168
208
|
[resolvedView],
|
|
169
209
|
);
|
|
170
210
|
|
|
171
|
-
const
|
|
211
|
+
const run = useCallback((runId: string): RunInfo | undefined => resolvedView?.run(runId), [resolvedView]);
|
|
172
212
|
|
|
173
|
-
const
|
|
213
|
+
const runs = useCallback((): RunInfo[] => resolvedView?.runs() ?? [], [resolvedView]);
|
|
174
214
|
|
|
175
|
-
|
|
215
|
+
// Branch navigation
|
|
216
|
+
const branchSelection = useCallback(
|
|
217
|
+
(codecMessageId: string): BranchSelection<TMessage> =>
|
|
218
|
+
// CAST: `EMPTY_BRANCH_SELECTION` is typed `BranchSelection<never>`; `never` is
|
|
219
|
+
// assignable to any `TMessage`, so the empty bundle is a valid fallback for
|
|
220
|
+
// the not-yet-resolved view case.
|
|
221
|
+
resolvedView?.branchSelection(codecMessageId) ?? (EMPTY_BRANCH_SELECTION as BranchSelection<TMessage>),
|
|
222
|
+
[resolvedView],
|
|
223
|
+
);
|
|
176
224
|
|
|
177
|
-
const
|
|
225
|
+
const selectSibling = useCallback(
|
|
226
|
+
(codecMessageId: string, index: number) => {
|
|
227
|
+
resolvedView?.selectSibling(codecMessageId, index);
|
|
228
|
+
},
|
|
229
|
+
[resolvedView],
|
|
230
|
+
);
|
|
178
231
|
|
|
179
232
|
// Write operation callbacks
|
|
180
233
|
const send = useCallback(
|
|
181
|
-
async (
|
|
234
|
+
async (events: TInput | TInput[], opts?: SendOptions) => {
|
|
182
235
|
if (!resolvedView)
|
|
183
236
|
throw new Ably.ErrorInfo('unable to send; view is not available', ErrorCode.InvalidArgument, 400);
|
|
184
|
-
return resolvedView.send(
|
|
237
|
+
return resolvedView.send(events, opts);
|
|
185
238
|
},
|
|
186
239
|
[resolvedView],
|
|
187
240
|
);
|
|
@@ -196,38 +249,27 @@ export const useView = <TEvent, TMessage>({
|
|
|
196
249
|
);
|
|
197
250
|
|
|
198
251
|
const edit = useCallback(
|
|
199
|
-
async (messageId: string,
|
|
252
|
+
async (messageId: string, inputs: TInput | TInput[], opts?: SendOptions) => {
|
|
200
253
|
if (!resolvedView)
|
|
201
254
|
throw new Ably.ErrorInfo('unable to edit; view is not available', ErrorCode.InvalidArgument, 400);
|
|
202
|
-
return resolvedView.edit(messageId,
|
|
203
|
-
},
|
|
204
|
-
[resolvedView],
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
const update = useCallback(
|
|
208
|
-
async (msgId: string, events: TEvent[], opts?: SendOptions) => {
|
|
209
|
-
if (!resolvedView)
|
|
210
|
-
throw new Ably.ErrorInfo('unable to update; view is not available', ErrorCode.InvalidArgument, 400);
|
|
211
|
-
return resolvedView.update(msgId, events, opts);
|
|
255
|
+
return resolvedView.edit(messageId, inputs, opts);
|
|
212
256
|
},
|
|
213
257
|
[resolvedView],
|
|
214
258
|
);
|
|
215
259
|
|
|
216
260
|
return {
|
|
217
261
|
messages,
|
|
218
|
-
nodes,
|
|
219
262
|
hasOlder,
|
|
220
263
|
loading,
|
|
221
264
|
loadError,
|
|
222
265
|
loadOlder,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
266
|
+
runOf,
|
|
267
|
+
run,
|
|
268
|
+
runs,
|
|
269
|
+
branchSelection,
|
|
270
|
+
selectSibling,
|
|
228
271
|
send,
|
|
229
272
|
regenerate,
|
|
230
273
|
edit,
|
|
231
|
-
update,
|
|
232
274
|
};
|
|
233
275
|
};
|
package/src/utils.ts
CHANGED
|
@@ -6,26 +6,68 @@
|
|
|
6
6
|
* the other.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import * as Ably from 'ably';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Extract a human-readable message from an unknown thrown value.
|
|
13
|
+
* @param error - The thrown value.
|
|
14
|
+
* @returns The error's `message` when it is an `Error`, otherwise its string form.
|
|
15
|
+
*/
|
|
16
|
+
export const errorMessage = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
19
|
+
* Narrow an unknown thrown value to an `Ably.ErrorInfo` for use as a wrapping
|
|
20
|
+
* `cause`, returning `undefined` when it is not one. Pass the result as the
|
|
21
|
+
* fourth argument to the `Ably.ErrorInfo` constructor to preserve the error
|
|
22
|
+
* chain without asserting a type the value may not have.
|
|
23
|
+
* @param error - The thrown value.
|
|
24
|
+
* @returns The value when it is an `Ably.ErrorInfo`, otherwise `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
export const errorCause = (error: unknown): Ably.ErrorInfo | undefined =>
|
|
27
|
+
error instanceof Ably.ErrorInfo ? error : undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read one tier of the SDK's `extras.ai` namespace from an Ably message.
|
|
31
|
+
* `extras.ai` is the SDK's reserved corner of the message envelope, split into
|
|
32
|
+
* a `transport` tier (generic transport headers) and a `codec` tier (codec
|
|
33
|
+
* headers). The application's own `extras.headers` is deliberately left
|
|
34
|
+
* untouched.
|
|
35
|
+
* @param message - The Ably message to read from.
|
|
36
|
+
* @param tier - Which `extras.ai` sub-namespace to read.
|
|
37
|
+
* @returns The tier's headers record, or an empty object if absent.
|
|
17
38
|
*/
|
|
18
|
-
|
|
39
|
+
const getAiTier = (message: Ably.InboundMessage, tier: 'transport' | 'codec'): Record<string, string> => {
|
|
19
40
|
// CAST: Ably SDK types `extras` as `any`; runtime checks below guard access.
|
|
20
41
|
const extras = message.extras as unknown;
|
|
21
42
|
if (!extras || typeof extras !== 'object') return {};
|
|
22
|
-
const
|
|
23
|
-
if (!
|
|
24
|
-
|
|
43
|
+
const ai = (extras as { ai?: unknown }).ai;
|
|
44
|
+
if (!ai || typeof ai !== 'object') return {};
|
|
45
|
+
const sub = (ai as Record<string, unknown>)[tier];
|
|
46
|
+
if (!sub || typeof sub !== 'object') return {};
|
|
47
|
+
// CAST: Ably wire protocol guarantees the tier is Record<string, string>
|
|
25
48
|
// when present, verified by the runtime guards above.
|
|
26
|
-
return
|
|
49
|
+
return sub as Record<string, string>;
|
|
27
50
|
};
|
|
28
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Extract the transport-tier headers (`extras.ai.transport`) from an Ably
|
|
54
|
+
* InboundMessage. These are the generic transport headers (run/stream/identity/
|
|
55
|
+
* branching), set and read by the transport layer.
|
|
56
|
+
* @param message - The Ably message to extract headers from.
|
|
57
|
+
* @returns The transport headers record, or an empty object if absent.
|
|
58
|
+
*/
|
|
59
|
+
export const getTransportHeaders = (message: Ably.InboundMessage): Record<string, string> =>
|
|
60
|
+
getAiTier(message, 'transport');
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract the codec-tier headers (`extras.ai.codec`) from an Ably
|
|
64
|
+
* InboundMessage. These are the codec's own headers, with no prefix — the
|
|
65
|
+
* tier isolates them from transport headers.
|
|
66
|
+
* @param message - The Ably message to extract headers from.
|
|
67
|
+
* @returns The codec headers record, or an empty object if absent.
|
|
68
|
+
*/
|
|
69
|
+
export const getCodecHeaders = (message: Ably.InboundMessage): Record<string, string> => getAiTier(message, 'codec');
|
|
70
|
+
|
|
29
71
|
/**
|
|
30
72
|
* Parse a JSON string, returning undefined on failure.
|
|
31
73
|
* @param value - The JSON string to parse.
|
|
@@ -41,32 +83,19 @@ export const parseJson = (value: string | undefined): unknown => {
|
|
|
41
83
|
};
|
|
42
84
|
|
|
43
85
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param
|
|
48
|
-
* @
|
|
86
|
+
* Parse a string as JSON, falling back to the raw string when it isn't valid
|
|
87
|
+
* JSON. An empty string yields `undefined`. Used for accumulated stream text
|
|
88
|
+
* whose payload may be JSON or a plain string.
|
|
89
|
+
* @param value - The string to parse.
|
|
90
|
+
* @returns The parsed value, the raw string on parse failure, or undefined if empty.
|
|
49
91
|
*/
|
|
50
|
-
export const
|
|
51
|
-
if (value
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
headers[key] = JSON.stringify(value);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Set multiple headers at once, skipping entries whose values are undefined or null.
|
|
63
|
-
* Each value is converted using the same rules as {@link setIfPresent}.
|
|
64
|
-
* @param headers - The headers object to mutate.
|
|
65
|
-
* @param entries - Key-value pairs to set.
|
|
66
|
-
*/
|
|
67
|
-
export const setHeadersIfPresent = (headers: Record<string, string>, entries: Record<string, unknown>): void => {
|
|
68
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
69
|
-
setIfPresent(headers, key, value);
|
|
92
|
+
export const parseJsonOrString = (value: string): unknown => {
|
|
93
|
+
if (!value) return undefined;
|
|
94
|
+
try {
|
|
95
|
+
// CAST: JSON.parse returns any; unknown is the safe trust-boundary type.
|
|
96
|
+
return JSON.parse(value) as unknown;
|
|
97
|
+
} catch {
|
|
98
|
+
return value;
|
|
70
99
|
}
|
|
71
100
|
};
|
|
72
101
|
|
|
@@ -95,30 +124,28 @@ export const parseBool = (value: string | undefined): boolean | undefined => {
|
|
|
95
124
|
return value === 'true';
|
|
96
125
|
};
|
|
97
126
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* @param entries - Unprefixed key-value pairs (e.g. `{ toolCallId: 'tc-1' }` becomes `{ 'x-domain-toolCallId': 'tc-1' }`).
|
|
104
|
-
* @returns A new headers record with prefixed keys.
|
|
105
|
-
*/
|
|
106
|
-
export const domainHeaders = (entries: Record<string, unknown>): Record<string, string> => {
|
|
107
|
-
const h: Record<string, string> = {};
|
|
108
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
109
|
-
setIfPresent(h, DOMAIN_HEADER_PREFIX + key, value);
|
|
110
|
-
}
|
|
111
|
-
return h;
|
|
112
|
-
};
|
|
127
|
+
/** A record carrying an optional Ably `serial`, orderable by {@link compareBySerial}. */
|
|
128
|
+
interface HasSerial {
|
|
129
|
+
/** Ably serial, or undefined if the server has not yet assigned one. */
|
|
130
|
+
readonly serial?: string;
|
|
131
|
+
}
|
|
113
132
|
|
|
114
133
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* @
|
|
134
|
+
* Comparator that orders records by their Ably `serial` ascending
|
|
135
|
+
* (chronological). Serials are lexicographically comparable; records whose
|
|
136
|
+
* serial is undefined sort last. Pass directly to `Array.prototype.sort`.
|
|
137
|
+
* @param a - First record to compare.
|
|
138
|
+
* @param b - Second record to compare.
|
|
139
|
+
* @returns Negative if `a` precedes `b`, positive if `a` follows `b`, 0 if equal.
|
|
119
140
|
*/
|
|
120
|
-
export const
|
|
121
|
-
|
|
141
|
+
export const compareBySerial = (a: HasSerial, b: HasSerial): number => {
|
|
142
|
+
if (a.serial === undefined && b.serial === undefined) return 0;
|
|
143
|
+
if (a.serial === undefined) return 1;
|
|
144
|
+
if (b.serial === undefined) return -1;
|
|
145
|
+
if (a.serial < b.serial) return -1;
|
|
146
|
+
if (a.serial > b.serial) return 1;
|
|
147
|
+
return 0;
|
|
148
|
+
};
|
|
122
149
|
|
|
123
150
|
/**
|
|
124
151
|
* Mapped type that converts properties whose type includes `undefined`
|
|
@@ -153,78 +180,3 @@ export const stripUndefined = <T extends Record<string, unknown>>(obj: T): Strip
|
|
|
153
180
|
// required keys are always present, optional keys are absent when undefined.
|
|
154
181
|
return result as Stripped<T>;
|
|
155
182
|
};
|
|
156
|
-
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
// DomainHeaderReader — typed accessors for domain headers
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Typed accessor wrapper around a headers record for reading domain headers.
|
|
163
|
-
* Reduces repetitive `getDomainHeader` + `parseBool` / `parseJson` chains.
|
|
164
|
-
*/
|
|
165
|
-
export interface DomainHeaderReader {
|
|
166
|
-
/** Read a domain header as a string, or undefined if absent. */
|
|
167
|
-
str(key: string): string | undefined;
|
|
168
|
-
/** Read a domain header as a string, falling back to a default if absent. */
|
|
169
|
-
strOr(key: string, fallback: string): string;
|
|
170
|
-
/** Read a domain header as a boolean ("true"/"false"), or undefined if absent. */
|
|
171
|
-
bool(key: string): boolean | undefined;
|
|
172
|
-
/** Read a domain header as parsed JSON, or undefined if absent or invalid. */
|
|
173
|
-
json(key: string): unknown;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Create a {@link DomainHeaderReader} over a headers record.
|
|
178
|
-
* @param headers - The raw headers record to read domain headers from.
|
|
179
|
-
* @returns A typed accessor for domain header values.
|
|
180
|
-
*/
|
|
181
|
-
export const headerReader = (headers: Record<string, string>): DomainHeaderReader => ({
|
|
182
|
-
str: (key: string) => getDomainHeader(headers, key),
|
|
183
|
-
strOr: (key: string, fallback: string) => getDomainHeader(headers, key) ?? fallback,
|
|
184
|
-
bool: (key: string) => parseBool(getDomainHeader(headers, key)),
|
|
185
|
-
json: (key: string) => parseJson(getDomainHeader(headers, key)),
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// DomainHeaderWriter — typed builder for domain headers
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Fluent builder for constructing domain header records with typed setters.
|
|
194
|
-
* Mirrors {@link DomainHeaderReader} with the same method names for symmetry.
|
|
195
|
-
* Undefined values are silently skipped on all setters.
|
|
196
|
-
*/
|
|
197
|
-
export interface DomainHeaderWriter {
|
|
198
|
-
/** Set a string domain header. Skips if value is undefined. */
|
|
199
|
-
str(key: string, value: string | undefined): DomainHeaderWriter;
|
|
200
|
-
/** Set a boolean domain header (serialized as "true"/"false"). Skips if value is undefined. */
|
|
201
|
-
bool(key: string, value: boolean | undefined): DomainHeaderWriter;
|
|
202
|
-
/** Set a JSON-serialized domain header. Skips if value is undefined or null. */
|
|
203
|
-
json(key: string, value: unknown): DomainHeaderWriter;
|
|
204
|
-
/** Return the accumulated headers record. */
|
|
205
|
-
build(): Record<string, string>;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Create a {@link DomainHeaderWriter} for building a domain headers record.
|
|
210
|
-
* @returns A fluent builder that prefixes each key with the domain header prefix.
|
|
211
|
-
*/
|
|
212
|
-
export const headerWriter = (): DomainHeaderWriter => {
|
|
213
|
-
const h: Record<string, string> = {};
|
|
214
|
-
const writer: DomainHeaderWriter = {
|
|
215
|
-
str: (key: string, value: string | undefined) => {
|
|
216
|
-
if (value !== undefined) h[DOMAIN_HEADER_PREFIX + key] = value;
|
|
217
|
-
return writer;
|
|
218
|
-
},
|
|
219
|
-
bool: (key: string, value: boolean | undefined) => {
|
|
220
|
-
if (value !== undefined) h[DOMAIN_HEADER_PREFIX + key] = String(value);
|
|
221
|
-
return writer;
|
|
222
|
-
},
|
|
223
|
-
json: (key: string, value: unknown) => {
|
|
224
|
-
if (value !== undefined && value !== null) h[DOMAIN_HEADER_PREFIX + key] = JSON.stringify(value);
|
|
225
|
-
return writer;
|
|
226
|
-
},
|
|
227
|
-
build: () => h,
|
|
228
|
-
};
|
|
229
|
-
return writer;
|
|
230
|
-
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel decode lifecycle policy — mid-stream-join repair.
|
|
3
|
+
*
|
|
4
|
+
* When a client joins a stream mid-flight (history compaction, rewind miss,
|
|
5
|
+
* partial page), the reducer must still see a clean `start` / `start-step`
|
|
6
|
+
* pre-roll. This policy keys that repair on the discrete codec `kind` and on
|
|
7
|
+
* stream start; each entry performs its tracker side effect and returns the
|
|
8
|
+
* lead-in chunks the generic decoder prepends before running the descriptor
|
|
9
|
+
* driver. A fresh policy (and tracker) is built per decoder instance.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type * as AI from 'ai';
|
|
13
|
+
|
|
14
|
+
import { createLifecycleTracker, type LifecyclePolicy, type LifecycleTracker } from '../../core/codec/index.js';
|
|
15
|
+
import { stripUndefined } from '../../utils.js';
|
|
16
|
+
import type { VercelOutput } from './events.js';
|
|
17
|
+
import { fMessageId } from './fields.js';
|
|
18
|
+
|
|
19
|
+
const createVercelLifecycleTracker = (): LifecycleTracker<AI.UIMessageChunk> =>
|
|
20
|
+
createLifecycleTracker<AI.UIMessageChunk>([
|
|
21
|
+
{
|
|
22
|
+
key: 'start',
|
|
23
|
+
build: (ctx) => [stripUndefined({ type: 'start' as const, messageId: ctx.messageId })],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: 'start-step',
|
|
27
|
+
build: () => [{ type: 'start-step' as const }],
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build a fresh Vercel decode lifecycle policy (with its own tracker). Passed
|
|
33
|
+
* to `defineCodec` as the `decodeLifecycle` factory so each decoder instance
|
|
34
|
+
* gets independent per-run phase state.
|
|
35
|
+
* @returns A {@link LifecyclePolicy} for the Vercel output union.
|
|
36
|
+
*/
|
|
37
|
+
export const createVercelDecodeLifecycle = (): LifecyclePolicy<VercelOutput> => {
|
|
38
|
+
const tracker = createVercelLifecycleTracker();
|
|
39
|
+
return {
|
|
40
|
+
onDiscrete: {
|
|
41
|
+
start: (runId) => {
|
|
42
|
+
tracker.markEmitted(runId, 'start');
|
|
43
|
+
return [];
|
|
44
|
+
},
|
|
45
|
+
'start-step': (runId) => {
|
|
46
|
+
tracker.markEmitted(runId, 'start-step');
|
|
47
|
+
return [];
|
|
48
|
+
},
|
|
49
|
+
'finish-step': (runId) => {
|
|
50
|
+
tracker.resetPhase(runId, 'start-step');
|
|
51
|
+
return [];
|
|
52
|
+
},
|
|
53
|
+
finish: (runId) => {
|
|
54
|
+
tracker.clearScope(runId);
|
|
55
|
+
return [];
|
|
56
|
+
},
|
|
57
|
+
error: (runId) => {
|
|
58
|
+
tracker.clearScope(runId);
|
|
59
|
+
return [];
|
|
60
|
+
},
|
|
61
|
+
abort: (runId) => {
|
|
62
|
+
tracker.clearScope(runId);
|
|
63
|
+
return [];
|
|
64
|
+
},
|
|
65
|
+
'tool-input': (runId, ctx) => tracker.ensurePhases(runId, { messageId: fMessageId.read(ctx.codecHeaders) }),
|
|
66
|
+
},
|
|
67
|
+
onStreamStart: (runId, trackerState) =>
|
|
68
|
+
tracker.ensurePhases(runId, { messageId: fMessageId.read(trackerState.codecHeaders) }),
|
|
69
|
+
};
|
|
70
|
+
};
|