@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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel codec input/output unions.
|
|
3
|
+
*
|
|
4
|
+
* The codec splits cleanly along the protocol's `ai-input` / `ai-output`
|
|
5
|
+
* wire seam:
|
|
6
|
+
*
|
|
7
|
+
* - **`VercelOutput`** = `AI.UIMessageChunk` — the AI SDK's streamed-output
|
|
8
|
+
* domain model, published by the agent on `ai-output`.
|
|
9
|
+
* - **`VercelInput`** = a discriminated union of the SDK's well-known
|
|
10
|
+
* input shapes — published by the client on `ai-input`. The Vercel
|
|
11
|
+
* codec has no codec-local input variants today: every variant comes
|
|
12
|
+
* from `@ably/ai-transport`'s well-known set.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type * as AI from 'ai';
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
Regenerate,
|
|
19
|
+
ToolApprovalResponse,
|
|
20
|
+
ToolResult,
|
|
21
|
+
ToolResultError,
|
|
22
|
+
UserMessage,
|
|
23
|
+
} from '../../core/codec/index.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Domain payloads
|
|
27
|
+
//
|
|
28
|
+
// The core well-known tool variants are domain-independent: the Vercel
|
|
29
|
+
// layer supplies the concrete payload shapes. Tool outputs are inherently
|
|
30
|
+
// tool-defined, so `output` stays `unknown` — but confined here, never in
|
|
31
|
+
// the core.
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** Vercel domain payload for a {@link ToolResult}. */
|
|
35
|
+
export interface VercelToolResultPayload {
|
|
36
|
+
/** The tool call this result corresponds to. */
|
|
37
|
+
toolCallId: string;
|
|
38
|
+
/** The tool's output value. Tool-defined shape. */
|
|
39
|
+
output: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Vercel domain payload for a {@link ToolResultError}. */
|
|
43
|
+
export interface VercelToolResultErrorPayload {
|
|
44
|
+
/** The tool call this error corresponds to. */
|
|
45
|
+
toolCallId: string;
|
|
46
|
+
/** Human-readable description of the failure. */
|
|
47
|
+
message: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Vercel domain payload for a {@link ToolApprovalResponse}. */
|
|
51
|
+
export interface VercelToolApprovalResponsePayload {
|
|
52
|
+
/** The tool call this approval responds to. */
|
|
53
|
+
toolCallId: string;
|
|
54
|
+
/** Whether the user approved the tool execution. */
|
|
55
|
+
approved: boolean;
|
|
56
|
+
/** Optional human-readable reason (typically used on denial). */
|
|
57
|
+
reason?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Unions
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The Vercel codec's `TInput` — every record-shape a client publishes on
|
|
66
|
+
* the `ai-input` wire. Composed from the SDK's well-known input shapes,
|
|
67
|
+
* with the tool variants parameterized by the Vercel domain payloads above.
|
|
68
|
+
*/
|
|
69
|
+
export type VercelInput =
|
|
70
|
+
| UserMessage<AI.UIMessage>
|
|
71
|
+
| Regenerate
|
|
72
|
+
| ToolResult<VercelToolResultPayload>
|
|
73
|
+
| ToolResultError<VercelToolResultErrorPayload>
|
|
74
|
+
| ToolApprovalResponse<VercelToolApprovalResponsePayload>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The Vercel codec's `TOutput` — every record-shape the agent publishes
|
|
78
|
+
* on the `ai-output` wire. The Vercel codec passes the AI SDK's
|
|
79
|
+
* `UIMessageChunk` through unchanged.
|
|
80
|
+
*/
|
|
81
|
+
export type VercelOutput = AI.UIMessageChunk;
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Projection re-export
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Vercel codec header-field bindings.
|
|
3
|
+
*
|
|
4
|
+
* Each field binds a codec header key to its value type once (see
|
|
5
|
+
* {@link HeaderField}); the output/input descriptors and escape hatches all
|
|
6
|
+
* read and write through these bindings, so a header key cannot drift between
|
|
7
|
+
* the encode and decode side. Domain field names live in the Vercel layer, not
|
|
8
|
+
* core, per the header-discipline rule.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type * as AI from 'ai';
|
|
12
|
+
|
|
13
|
+
import { boolField, enumField, type HeaderField, jsonField, strField } from '../../core/codec/index.js';
|
|
14
|
+
|
|
15
|
+
/** Stream / message id (text & reasoning streams). */
|
|
16
|
+
export const fId = strField('id');
|
|
17
|
+
/**
|
|
18
|
+
* Provider metadata envelope, typed to the AI SDK shape. Annotated explicitly:
|
|
19
|
+
* the inferred type resolves to the AI SDK's internal `SharedV3ProviderMetadata`
|
|
20
|
+
* alias, which isn't portably nameable across the package boundary.
|
|
21
|
+
*/
|
|
22
|
+
export const fMeta: HeaderField<AI.ProviderMetadata | undefined, 'providerMetadata'> = jsonField<
|
|
23
|
+
AI.ProviderMetadata,
|
|
24
|
+
'providerMetadata'
|
|
25
|
+
>('providerMetadata');
|
|
26
|
+
/** Tool call id — defaulted to total: an absent header reads as `''`. */
|
|
27
|
+
export const fToolCallId = strField('toolCallId', '');
|
|
28
|
+
/** Tool name — defaulted to total. */
|
|
29
|
+
export const fToolName = strField('toolName', '');
|
|
30
|
+
/** Whether the tool is a dynamic tool. */
|
|
31
|
+
export const fDynamic = boolField('dynamic');
|
|
32
|
+
/** Optional human-readable title. */
|
|
33
|
+
export const fTitle = strField('title');
|
|
34
|
+
/** Whether the provider executed the tool. */
|
|
35
|
+
export const fProviderExecuted = boolField('providerExecuted');
|
|
36
|
+
/** Media type for file / source-document parts — defaulted to total. */
|
|
37
|
+
export const fMediaType = strField('mediaType', '');
|
|
38
|
+
/** Source id for source-url / source-document parts — defaulted to total. */
|
|
39
|
+
export const fSourceId = strField('sourceId', '');
|
|
40
|
+
|
|
41
|
+
// --- input-side bindings (shared by the input descriptors' encode/decode) ---
|
|
42
|
+
|
|
43
|
+
/** Domain message id (`message.id`) stamped on every user-message part — distinct from the wire codec-message-id transport header. */
|
|
44
|
+
export const fMessageId = strField('messageId');
|
|
45
|
+
/** Whether the user approved a tool execution — defaulted to total so an absent header reads `false`. */
|
|
46
|
+
export const fApproved = boolField('approved', false);
|
|
47
|
+
/** Optional human-readable reason on a tool-approval response. */
|
|
48
|
+
export const fReason = strField('reason');
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validated finish reason. Mirrors the AI SDK's `FinishReason` literals and
|
|
52
|
+
* falls back to `'stop'` for an absent or unrecognized value.
|
|
53
|
+
*/
|
|
54
|
+
export const fFinishReason = enumField(
|
|
55
|
+
'finishReason',
|
|
56
|
+
['stop', 'length', 'content-filter', 'tool-calls', 'error', 'other'] as const,
|
|
57
|
+
'stop',
|
|
58
|
+
);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File and source content-part folds: file / source-url / source-document.
|
|
3
|
+
* These are independent attachments — each appends a part, never dedups.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type * as AI from 'ai';
|
|
7
|
+
|
|
8
|
+
import { stripUndefined } from '../../utils.js';
|
|
9
|
+
import { ensureMessage, type VercelProjection } from './reducer-state.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fold a file or source content chunk into the projection.
|
|
13
|
+
* @param state - Projection to fold into.
|
|
14
|
+
* @param chunk - The file, source-url, or source-document chunk.
|
|
15
|
+
* @param messageId - The target codec-message-id.
|
|
16
|
+
* @returns The same projection reference.
|
|
17
|
+
*/
|
|
18
|
+
export const foldContentPart = (
|
|
19
|
+
state: VercelProjection,
|
|
20
|
+
chunk: Extract<AI.UIMessageChunk, { type: 'file' | 'source-url' | 'source-document' }>,
|
|
21
|
+
messageId: string,
|
|
22
|
+
): VercelProjection => {
|
|
23
|
+
const message = ensureMessage(state, messageId);
|
|
24
|
+
|
|
25
|
+
switch (chunk.type) {
|
|
26
|
+
case 'file': {
|
|
27
|
+
message.parts.push({ type: 'file', mediaType: chunk.mediaType, url: chunk.url });
|
|
28
|
+
return state;
|
|
29
|
+
}
|
|
30
|
+
case 'source-url': {
|
|
31
|
+
message.parts.push(
|
|
32
|
+
stripUndefined({
|
|
33
|
+
type: 'source-url' as const,
|
|
34
|
+
sourceId: chunk.sourceId,
|
|
35
|
+
url: chunk.url,
|
|
36
|
+
title: chunk.title,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
case 'source-document': {
|
|
42
|
+
message.parts.push(
|
|
43
|
+
stripUndefined({
|
|
44
|
+
type: 'source-document' as const,
|
|
45
|
+
sourceId: chunk.sourceId,
|
|
46
|
+
mediaType: chunk.mediaType,
|
|
47
|
+
title: chunk.title,
|
|
48
|
+
filename: chunk.filename,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* data-* part folds. Transient data parts are dropped; persistent ones are
|
|
3
|
+
* appended, or replaced in place when a matching `id` is already present.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type * as AI from 'ai';
|
|
7
|
+
|
|
8
|
+
import { stripUndefined } from '../../utils.js';
|
|
9
|
+
import { ensureMessage, type VercelProjection } from './reducer-state.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fold a `data-*` chunk into the projection.
|
|
13
|
+
* @param state - Projection to fold into.
|
|
14
|
+
* @param chunk - The data-* chunk.
|
|
15
|
+
* @param messageId - The target codec-message-id.
|
|
16
|
+
* @returns The same projection reference.
|
|
17
|
+
*/
|
|
18
|
+
export const foldDataPart = (
|
|
19
|
+
state: VercelProjection,
|
|
20
|
+
chunk: Extract<AI.UIMessageChunk, { type: `data-${string}` }>,
|
|
21
|
+
messageId: string,
|
|
22
|
+
): VercelProjection => {
|
|
23
|
+
if (chunk.transient) return state;
|
|
24
|
+
|
|
25
|
+
const message = ensureMessage(state, messageId);
|
|
26
|
+
|
|
27
|
+
// CAST: chunk.type is `data-${string}` which satisfies DataUIPart, but
|
|
28
|
+
// TypeScript cannot verify the template literal matches a specific
|
|
29
|
+
// UIMessagePart variant at the type level.
|
|
30
|
+
const dataPart = stripUndefined({
|
|
31
|
+
type: chunk.type,
|
|
32
|
+
id: chunk.id,
|
|
33
|
+
data: chunk.data,
|
|
34
|
+
}) as AI.UIMessage['parts'][number];
|
|
35
|
+
|
|
36
|
+
if (chunk.id !== undefined) {
|
|
37
|
+
const idx = message.parts.findIndex((p) => p.type === chunk.type && 'id' in p && p.id === chunk.id);
|
|
38
|
+
if (idx !== -1) {
|
|
39
|
+
message.parts[idx] = dataPart;
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
message.parts.push(dataPart);
|
|
45
|
+
return state;
|
|
46
|
+
};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-published input folds and the pending-resolution buffering.
|
|
3
|
+
*
|
|
4
|
+
* Tool resolutions (`ToolResult`, `ToolResultError`, `ToolApprovalResponse`)
|
|
5
|
+
* carry a `codecMessageId` targeting the assistant they amend. When that
|
|
6
|
+
* assistant (or its tool part) has not yet arrived, the resolution is buffered
|
|
7
|
+
* in `pendingToolResolutions` and {@link retryPendingResolutions} re-evaluates
|
|
8
|
+
* it after every subsequent fold.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type * as AI from 'ai';
|
|
12
|
+
|
|
13
|
+
import type { ReducerMeta, ToolApprovalResponse, ToolResult, ToolResultError } from '../../core/codec/index.js';
|
|
14
|
+
import type {
|
|
15
|
+
VercelToolApprovalResponsePayload,
|
|
16
|
+
VercelToolResultErrorPayload,
|
|
17
|
+
VercelToolResultPayload,
|
|
18
|
+
} from './events.js';
|
|
19
|
+
import {
|
|
20
|
+
ensureTrackers,
|
|
21
|
+
getToolPart,
|
|
22
|
+
type OwnerLookup,
|
|
23
|
+
type PendingToolResolution,
|
|
24
|
+
type VercelProjection,
|
|
25
|
+
} from './reducer-state.js';
|
|
26
|
+
import { toolBase, transitionToolPart } from './tool-transitions.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fold a user message into the projection, correlating on the wire
|
|
30
|
+
* codec-message-id (the caller's `message.id` is preserved verbatim). A
|
|
31
|
+
* multi-part user message fans out into one wire event per part, all sharing
|
|
32
|
+
* the codec-message-id — folding appends the incoming parts to the existing
|
|
33
|
+
* entry, reassembling the message part by part. The transport delivers each
|
|
34
|
+
* wire exactly once (its per-message version high-water-mark drops replays),
|
|
35
|
+
* so the merge sees every part once and stays consistent.
|
|
36
|
+
*
|
|
37
|
+
* Optimistic (serial-less) seeds need no special handling here: the transport
|
|
38
|
+
* refolds the node from its log when the echo's serial arrives, rebuilding the
|
|
39
|
+
* projection from a fresh `init` so the seed never coexists with its echo.
|
|
40
|
+
* @param state - Projection to fold into.
|
|
41
|
+
* @param message - The user message (or one decoded part of it) to add or merge.
|
|
42
|
+
* @param meta - Transport-derived metadata carrying the codec-message-id.
|
|
43
|
+
* @returns The same projection reference.
|
|
44
|
+
*/
|
|
45
|
+
export const foldUserMessage = (
|
|
46
|
+
state: VercelProjection,
|
|
47
|
+
message: AI.UIMessage,
|
|
48
|
+
meta: ReducerMeta,
|
|
49
|
+
): VercelProjection => {
|
|
50
|
+
// Correlate the projection entry on the wire codec-message-id; the
|
|
51
|
+
// caller-supplied `message.id` is preserved verbatim and surfaced to the
|
|
52
|
+
// application unchanged. Without a codec-message-id the message has no
|
|
53
|
+
// identity to key on, so it is appended as a fresh entry.
|
|
54
|
+
const codecMessageId = meta.messageId;
|
|
55
|
+
if (codecMessageId === undefined) {
|
|
56
|
+
state.messages.push({ codecMessageId: message.id, message });
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
const existing = state.messages.find((e) => e.codecMessageId === codecMessageId);
|
|
60
|
+
if (existing === undefined) {
|
|
61
|
+
state.messages.push({ codecMessageId, message });
|
|
62
|
+
} else {
|
|
63
|
+
// Merge by codec-message-id: keep the existing envelope (id and role are
|
|
64
|
+
// stamped identically on every part of one message) and append the
|
|
65
|
+
// incoming parts in fold order — wire serials preserve publish order.
|
|
66
|
+
existing.message.parts.push(...message.parts);
|
|
67
|
+
}
|
|
68
|
+
return state;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Fold a client-published `ToolResult`. The input carries
|
|
73
|
+
* `codecMessageId` pointing at the assistant whose `dynamic-tool` part
|
|
74
|
+
* holds the matching `toolCallId`. If the assistant and its matching
|
|
75
|
+
* `dynamic-tool` part are both present, fold directly; otherwise pend
|
|
76
|
+
* until that tool part arrives.
|
|
77
|
+
* @param state - Projection to fold into.
|
|
78
|
+
* @param event - The tool-result input (codecMessageId + domain payload).
|
|
79
|
+
* @returns The same projection reference.
|
|
80
|
+
*/
|
|
81
|
+
export const foldClientToolResult = (
|
|
82
|
+
state: VercelProjection,
|
|
83
|
+
event: ToolResult<VercelToolResultPayload>,
|
|
84
|
+
): VercelProjection => {
|
|
85
|
+
const { toolCallId, output } = event.payload;
|
|
86
|
+
return resolveOrPend(state, event.codecMessageId, toolCallId, { kind: 'tool-result', output });
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fold a client-published `ToolResultError`. Mirrors
|
|
91
|
+
* {@link foldClientToolResult} but with the error transition.
|
|
92
|
+
* @param state - Projection to fold into.
|
|
93
|
+
* @param event - The tool-result-error input (codecMessageId + domain payload).
|
|
94
|
+
* @returns The same projection reference.
|
|
95
|
+
*/
|
|
96
|
+
export const foldClientToolResultError = (
|
|
97
|
+
state: VercelProjection,
|
|
98
|
+
event: ToolResultError<VercelToolResultErrorPayload>,
|
|
99
|
+
): VercelProjection => {
|
|
100
|
+
const { toolCallId, message } = event.payload;
|
|
101
|
+
return resolveOrPend(state, event.codecMessageId, toolCallId, { kind: 'tool-result-error', message });
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fold a client-published `ToolApprovalResponse`. The input carries
|
|
106
|
+
* `codecMessageId` pointing at the assistant whose `dynamic-tool` part
|
|
107
|
+
* holds the matching `toolCallId`. Approval → `approval-responded`;
|
|
108
|
+
* denial → `output-denied` via {@link transitionToolPart}.
|
|
109
|
+
* @param state - Projection to fold into.
|
|
110
|
+
* @param event - The approval-response input.
|
|
111
|
+
* @returns The same projection reference.
|
|
112
|
+
*/
|
|
113
|
+
export const foldToolApprovalResponse = (
|
|
114
|
+
state: VercelProjection,
|
|
115
|
+
event: ToolApprovalResponse<VercelToolApprovalResponsePayload>,
|
|
116
|
+
): VercelProjection => {
|
|
117
|
+
const { toolCallId, approved, reason } = event.payload;
|
|
118
|
+
return resolveOrPend(state, event.codecMessageId, toolCallId, {
|
|
119
|
+
kind: 'tool-approval-response',
|
|
120
|
+
approved,
|
|
121
|
+
...(reason === undefined ? {} : { reason }),
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Apply a resolution when its tool part is present, otherwise buffer it in
|
|
127
|
+
* `pendingToolResolutions` for {@link retryPendingResolutions}.
|
|
128
|
+
* @param state - Projection to fold into.
|
|
129
|
+
* @param codecMessageId - The assistant the resolution targets.
|
|
130
|
+
* @param toolCallId - The tool call being resolved.
|
|
131
|
+
* @param resolution - The resolution variant to apply or buffer.
|
|
132
|
+
* @returns The same projection reference.
|
|
133
|
+
*/
|
|
134
|
+
const resolveOrPend = (
|
|
135
|
+
state: VercelProjection,
|
|
136
|
+
codecMessageId: string,
|
|
137
|
+
toolCallId: string,
|
|
138
|
+
resolution: PendingToolResolution['resolution'],
|
|
139
|
+
): VercelProjection => {
|
|
140
|
+
const owner = findOwner(state, codecMessageId, toolCallId);
|
|
141
|
+
if (owner) {
|
|
142
|
+
applyResolution(owner, toolCallId, resolution);
|
|
143
|
+
} else {
|
|
144
|
+
state.pendingToolResolutions.push({ targetCodecMessageId: codecMessageId, toolCallId, resolution });
|
|
145
|
+
}
|
|
146
|
+
return state;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Apply one tool resolution onto its located `dynamic-tool` part, replacing
|
|
151
|
+
* the part with the transitioned shape — the single application point shared
|
|
152
|
+
* by the direct folds and {@link retryPendingResolutions}.
|
|
153
|
+
* @param owner - The located owner (message + tracker + part).
|
|
154
|
+
* @param toolCallId - The tool call being resolved.
|
|
155
|
+
* @param resolution - The resolution variant to apply.
|
|
156
|
+
*/
|
|
157
|
+
const applyResolution = (
|
|
158
|
+
owner: OwnerLookup,
|
|
159
|
+
toolCallId: string,
|
|
160
|
+
resolution: PendingToolResolution['resolution'],
|
|
161
|
+
): void => {
|
|
162
|
+
switch (resolution.kind) {
|
|
163
|
+
case 'tool-result': {
|
|
164
|
+
owner.message.parts[owner.tracker.partIndex] = transitionToolPart(owner.part, {
|
|
165
|
+
type: 'tool-output-available',
|
|
166
|
+
toolCallId,
|
|
167
|
+
output: resolution.output,
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'tool-result-error': {
|
|
172
|
+
owner.message.parts[owner.tracker.partIndex] = transitionToolPart(owner.part, {
|
|
173
|
+
type: 'tool-output-error',
|
|
174
|
+
toolCallId,
|
|
175
|
+
errorText: resolution.message,
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'tool-approval-response': {
|
|
180
|
+
owner.message.parts[owner.tracker.partIndex] = approvalTransition(
|
|
181
|
+
owner.part,
|
|
182
|
+
resolution.approved,
|
|
183
|
+
resolution.reason,
|
|
184
|
+
);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Re-attempt every pending tool resolution against the current projection.
|
|
192
|
+
* Successfully promoted entries are removed from the pending list. Cheap:
|
|
193
|
+
* bounded by the number of pending entries.
|
|
194
|
+
* @param state - Projection to walk and mutate.
|
|
195
|
+
*/
|
|
196
|
+
export const retryPendingResolutions = (state: VercelProjection): void => {
|
|
197
|
+
const next: VercelProjection['pendingToolResolutions'] = [];
|
|
198
|
+
for (const pending of state.pendingToolResolutions) {
|
|
199
|
+
const owner = findOwner(state, pending.targetCodecMessageId, pending.toolCallId);
|
|
200
|
+
if (!owner) {
|
|
201
|
+
next.push(pending);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
applyResolution(owner, pending.toolCallId, pending.resolution);
|
|
205
|
+
}
|
|
206
|
+
state.pendingToolResolutions = next;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const findOwner = (state: VercelProjection, codecMessageId: string, toolCallId: string): OwnerLookup | undefined => {
|
|
210
|
+
const entry = state.messages.find((e) => e.codecMessageId === codecMessageId);
|
|
211
|
+
if (!entry) return undefined;
|
|
212
|
+
const trackers = ensureTrackers(state, codecMessageId);
|
|
213
|
+
const found = getToolPart(entry.message, trackers, toolCallId);
|
|
214
|
+
if (!found) return undefined;
|
|
215
|
+
return { message: entry.message, tracker: found.tracker, part: found.part };
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Build the next `dynamic-tool` part shape for an approval response.
|
|
220
|
+
*
|
|
221
|
+
* For `approved=true`, transition to `approval-responded` so the AI SDK's
|
|
222
|
+
* multi-step loop will auto-run the tool on the next step.
|
|
223
|
+
* `transitionToolPart` has no shape for this transition, so we synthesize
|
|
224
|
+
* the part directly.
|
|
225
|
+
*
|
|
226
|
+
* For `approved=false`, delegate to `transitionToolPart` with a synthetic
|
|
227
|
+
* `tool-output-denied` chunk so denial mirrors the chunk-driven path.
|
|
228
|
+
* @param part - The existing `dynamic-tool` part being transitioned.
|
|
229
|
+
* @param approved - Whether the user approved the tool execution.
|
|
230
|
+
* @param reason - Optional human-readable reason.
|
|
231
|
+
* @returns The replacement `dynamic-tool` part.
|
|
232
|
+
*/
|
|
233
|
+
const approvalTransition = (
|
|
234
|
+
part: AI.DynamicToolUIPart,
|
|
235
|
+
approved: boolean,
|
|
236
|
+
reason: string | undefined,
|
|
237
|
+
): AI.DynamicToolUIPart => {
|
|
238
|
+
if (approved) {
|
|
239
|
+
return {
|
|
240
|
+
...toolBase(part),
|
|
241
|
+
state: 'approval-responded',
|
|
242
|
+
input: 'input' in part ? part.input : undefined,
|
|
243
|
+
approval: {
|
|
244
|
+
id: 'approval' in part && part.approval ? part.approval.id : '',
|
|
245
|
+
approved: true,
|
|
246
|
+
...(reason === undefined ? {} : { reason }),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return transitionToolPart(part, {
|
|
251
|
+
type: 'tool-output-denied',
|
|
252
|
+
toolCallId: part.toolCallId,
|
|
253
|
+
...(reason === undefined ? {} : { reason }),
|
|
254
|
+
});
|
|
255
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle chunk folds: start, start-step, finish-step, finish, abort,
|
|
3
|
+
* error, message-metadata.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type * as AI from 'ai';
|
|
7
|
+
|
|
8
|
+
import { ensureMessage, type VercelProjection } from './reducer-state.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Set a message's metadata from a chunk when both the message exists and the
|
|
12
|
+
* chunk carries metadata. Shared by the `finish` and `message-metadata` cases,
|
|
13
|
+
* which apply it identically. The `start` case is not routed through here — it
|
|
14
|
+
* creates the message via `ensureMessage` first.
|
|
15
|
+
* @param state - Projection holding the message.
|
|
16
|
+
* @param messageId - The target codec-message-id.
|
|
17
|
+
* @param metadata - The chunk's `messageMetadata`, or undefined to leave it unchanged.
|
|
18
|
+
*/
|
|
19
|
+
const applyMessageMetadata = (state: VercelProjection, messageId: string, metadata: AI.UIMessage['metadata']): void => {
|
|
20
|
+
if (metadata === undefined) return;
|
|
21
|
+
const message = state.messages.find((e) => e.codecMessageId === messageId)?.message;
|
|
22
|
+
if (message) message.metadata = metadata;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fold a message-lifecycle chunk into the projection.
|
|
27
|
+
* @param state - Projection to fold into.
|
|
28
|
+
* @param chunk - The lifecycle chunk.
|
|
29
|
+
* @param messageId - The target codec-message-id.
|
|
30
|
+
* @returns The same projection reference.
|
|
31
|
+
*/
|
|
32
|
+
export const foldLifecycle = (
|
|
33
|
+
state: VercelProjection,
|
|
34
|
+
chunk: Extract<
|
|
35
|
+
AI.UIMessageChunk,
|
|
36
|
+
{ type: 'start' | 'start-step' | 'finish-step' | 'finish' | 'abort' | 'error' | 'message-metadata' }
|
|
37
|
+
>,
|
|
38
|
+
messageId: string,
|
|
39
|
+
): VercelProjection => {
|
|
40
|
+
switch (chunk.type) {
|
|
41
|
+
case 'start': {
|
|
42
|
+
// The projection entry is keyed on the wire codec-message-id
|
|
43
|
+
// (`messageId`); every subsequent chunk for this message correlates on
|
|
44
|
+
// that, independent of `message.id`. So we faithfully reproduce the
|
|
45
|
+
// stream's own `messageId` on the reconstructed `UIMessage.id` (the
|
|
46
|
+
// value surfaced to the application) without risk of orphaning later
|
|
47
|
+
// chunks. When the stream omits it, the codec-message-id seeded by
|
|
48
|
+
// `ensureMessage` stands as the fallback id.
|
|
49
|
+
const message = ensureMessage(state, messageId);
|
|
50
|
+
if (chunk.messageId !== undefined) message.id = chunk.messageId;
|
|
51
|
+
if (chunk.messageMetadata !== undefined) message.metadata = chunk.messageMetadata;
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
case 'start-step': {
|
|
55
|
+
const message = ensureMessage(state, messageId);
|
|
56
|
+
message.parts.push({ type: 'step-start' });
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
case 'finish-step': {
|
|
60
|
+
// Reset text/reasoning stream trackers so a follow-up step can start
|
|
61
|
+
// new parts with potentially-reused stream ids.
|
|
62
|
+
const trackers = state.trackers.get(messageId);
|
|
63
|
+
if (trackers) {
|
|
64
|
+
trackers.text.clear();
|
|
65
|
+
trackers.reasoning.clear();
|
|
66
|
+
}
|
|
67
|
+
return state;
|
|
68
|
+
}
|
|
69
|
+
case 'finish': {
|
|
70
|
+
applyMessageMetadata(state, messageId, chunk.messageMetadata);
|
|
71
|
+
// Tracker state retained — late events still resolvable; cleanup happens at Run end.
|
|
72
|
+
return state;
|
|
73
|
+
}
|
|
74
|
+
case 'abort':
|
|
75
|
+
case 'error': {
|
|
76
|
+
// No state mutation — run termination is observed via the wire run-end
|
|
77
|
+
// event, not the projection.
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
case 'message-metadata': {
|
|
81
|
+
applyMessageMetadata(state, messageId, chunk.messageMetadata);
|
|
82
|
+
return state;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text and reasoning streaming folds: the {start, delta, end} lifecycle for
|
|
3
|
+
* both `text-*` and `reasoning-*` chunks, which share the same shape.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type * as AI from 'ai';
|
|
7
|
+
|
|
8
|
+
import { ensureMessage, ensureTrackers, type VercelProjection } from './reducer-state.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fold a text or reasoning streaming chunk into the projection.
|
|
12
|
+
* @param state - Projection to fold into.
|
|
13
|
+
* @param chunk - The text/reasoning start, delta, or end chunk.
|
|
14
|
+
* @param messageId - The target codec-message-id.
|
|
15
|
+
* @returns The same projection reference.
|
|
16
|
+
*/
|
|
17
|
+
export const foldTextOrReasoning = (
|
|
18
|
+
state: VercelProjection,
|
|
19
|
+
chunk: Extract<
|
|
20
|
+
AI.UIMessageChunk,
|
|
21
|
+
{ type: 'text-start' | 'text-delta' | 'text-end' | 'reasoning-start' | 'reasoning-delta' | 'reasoning-end' }
|
|
22
|
+
>,
|
|
23
|
+
messageId: string,
|
|
24
|
+
): VercelProjection => {
|
|
25
|
+
const message = ensureMessage(state, messageId);
|
|
26
|
+
const trackers = ensureTrackers(state, messageId);
|
|
27
|
+
|
|
28
|
+
const isText = chunk.type.startsWith('text-');
|
|
29
|
+
const partType = isText ? 'text' : 'reasoning';
|
|
30
|
+
const activeMap = isText ? trackers.text : trackers.reasoning;
|
|
31
|
+
|
|
32
|
+
switch (chunk.type) {
|
|
33
|
+
case 'text-start':
|
|
34
|
+
case 'reasoning-start': {
|
|
35
|
+
activeMap.set(chunk.id, message.parts.length);
|
|
36
|
+
message.parts.push({ type: partType, text: '' });
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
case 'text-delta':
|
|
40
|
+
case 'reasoning-delta': {
|
|
41
|
+
const idx = activeMap.get(chunk.id);
|
|
42
|
+
if (idx === undefined) return state;
|
|
43
|
+
const part = message.parts[idx];
|
|
44
|
+
if (part?.type === partType) {
|
|
45
|
+
part.text += chunk.delta;
|
|
46
|
+
}
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
case 'text-end':
|
|
50
|
+
case 'reasoning-end': {
|
|
51
|
+
activeMap.delete(chunk.id);
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|