@steerable/agent-ui 0.3.0 → 0.4.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/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useChatStream.d.ts +32 -0
- package/dist/hooks/useChatStream.d.ts.map +1 -1
- package/dist/hooks/useChatStream.js +58 -8
- package/dist/hooks/useChatStream.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useChatStream.mdx +23 -1
- package/src/hooks/useChatStream.test.ts +241 -1
- package/src/hooks/useChatStream.ts +104 -9
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useChatStream } from './useChatStream.js';
|
|
2
|
-
export type { UseChatStreamOptions, UseChatStreamReturn, ChatStreamTransport, ChatStreamSendInput, } from './useChatStream.js';
|
|
2
|
+
export type { UseChatStreamOptions, UseChatStreamReturn, ChatStreamTransport, ChatStreamSendInput, SteerOutcome, } from './useChatStream.js';
|
|
3
3
|
export { useToolCallStatus } from './useToolCallStatus.js';
|
|
4
4
|
export type { ToolCallStatus, UseToolCallStatusOptions, UseToolCallStatusReturn, } from './useToolCallStatus.js';
|
|
5
5
|
export { useAgentSession } from './useAgentSession.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,cAAc,EACd,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAO3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -35,6 +35,17 @@ export interface ChatStreamTransport {
|
|
|
35
35
|
*/
|
|
36
36
|
steer?: (content: string) => Promise<boolean>;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Where `steerOrFollowUpUserMessage` actually delivered the message:
|
|
40
|
+
* - `'steered'` — the running turn accepted the injection.
|
|
41
|
+
* - `'queued'` — the turn is still running but not steerable; the message
|
|
42
|
+
* sits in the W6-2 follow-up queue and auto-sends at turn end.
|
|
43
|
+
* - `'sent'` — no turn was running by the time the steer attempt settled;
|
|
44
|
+
* the message went out immediately as a normal new turn.
|
|
45
|
+
* The union lets callers render their own locale-specific notice per
|
|
46
|
+
* outcome; the hook itself carries no user-facing copy.
|
|
47
|
+
*/
|
|
48
|
+
export type SteerOutcome = 'steered' | 'queued' | 'sent';
|
|
38
49
|
export interface UseChatStreamOptions {
|
|
39
50
|
/** Required transport handle; see `ChatStreamTransport`. */
|
|
40
51
|
transport: ChatStreamTransport;
|
|
@@ -62,6 +73,17 @@ export interface UseChatStreamReturn {
|
|
|
62
73
|
* sent automatically as the next turn when the current one ends.
|
|
63
74
|
*/
|
|
64
75
|
sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* W7-1: resume the interrupted turn recorded on the backend. Streams an
|
|
78
|
+
* assistant turn WITHOUT appending a user message — the transport receives
|
|
79
|
+
* `{ content: '', metadata: { ...input?.metadata, resume: true } }` and the
|
|
80
|
+
* backend replays the durable record's projection as the loop seed, so the
|
|
81
|
+
* user never retypes the interrupted request. No-op while streaming (a
|
|
82
|
+
* resume is not queueable as a follow-up — it is not a user message).
|
|
83
|
+
*/
|
|
84
|
+
resumeTurn: (input?: {
|
|
85
|
+
metadata?: Record<string, unknown>;
|
|
86
|
+
}) => Promise<void>;
|
|
65
87
|
/**
|
|
66
88
|
* Steer the running turn with an extra user message. No-op resolving
|
|
67
89
|
* `false` when not streaming or the transport has no `steer`. On success
|
|
@@ -69,6 +91,16 @@ export interface UseChatStreamReturn {
|
|
|
69
91
|
* consumes it at the next round boundary).
|
|
70
92
|
*/
|
|
71
93
|
steerUserMessage: (content: string) => Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Steer with a never-drop fallback (W6-2): tries `steerUserMessage` first,
|
|
96
|
+
* then degrades based on live turn state — see `SteerOutcome` for the
|
|
97
|
+
* three outcomes. The turn-already-ended race is resolved by re-checking
|
|
98
|
+
* `isStreaming` after the steer attempt settles: a finished turn's
|
|
99
|
+
* `finally` has already drained the queue, so queueing there would strand
|
|
100
|
+
* the message until some future turn ends — it is sent directly instead.
|
|
101
|
+
* Prefer this over `steerUserMessage` for composer Enter handlers.
|
|
102
|
+
*/
|
|
103
|
+
steerOrFollowUpUserMessage: (content: string) => Promise<SteerOutcome>;
|
|
72
104
|
/**
|
|
73
105
|
* Explicitly queue a message as a follow-up (W6-2): sent automatically as
|
|
74
106
|
* the next turn once the current turn finishes. When not streaming this is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,IAAI,CAAC;IACjE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,0EAA0E;IAC1E,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,uEAAuE;IACvE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAsID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+
|
|
1
|
+
{"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,IAAI,CAAC;IACjE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;;;;OAQG;IACH,0BAA0B,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACvE;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,0EAA0E;IAC1E,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,uEAAuE;IACvE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAsID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+QrB"}
|
|
@@ -201,15 +201,14 @@ export function useChatStream(options) {
|
|
|
201
201
|
options.onUnknownEvent?.(event);
|
|
202
202
|
}
|
|
203
203
|
}, [options]);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
// Shared turn core: stream one assistant turn and drain the follow-up
|
|
205
|
+
// queue at the end. `echoUserMessage` is false only for W7-1 resume —
|
|
206
|
+
// the interrupted turn's user message is already in the transcript and
|
|
207
|
+
// the backend replays it from the durable record.
|
|
208
|
+
const startTurn = useCallback(async (input, opts) => {
|
|
209
|
+
if (opts.echoUserMessage) {
|
|
210
|
+
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
211
211
|
}
|
|
212
|
-
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
213
212
|
dispatch({ type: 'append', message: newAssistantPlaceholder() });
|
|
214
213
|
setStreaming(true);
|
|
215
214
|
try {
|
|
@@ -238,6 +237,21 @@ export function useChatStream(options) {
|
|
|
238
237
|
}
|
|
239
238
|
}
|
|
240
239
|
}, [handleEvent, options.transport, setStreaming]);
|
|
240
|
+
const sendUserMessage = useCallback(async (input) => {
|
|
241
|
+
if (isStreamingRef.current) {
|
|
242
|
+
// W6-2: queue instead of dropping. The finishing turn's `finally`
|
|
243
|
+
// drains the queue and starts the next turn automatically.
|
|
244
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
245
|
+
bumpFollowUpVersion();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
await startTurn(input, { echoUserMessage: true });
|
|
249
|
+
}, [startTurn]);
|
|
250
|
+
const resumeTurn = useCallback(async (input) => {
|
|
251
|
+
if (isStreamingRef.current)
|
|
252
|
+
return;
|
|
253
|
+
await startTurn({ content: '', metadata: { ...input?.metadata, resume: true } }, { echoUserMessage: false });
|
|
254
|
+
}, [startTurn]);
|
|
241
255
|
useEffect(() => {
|
|
242
256
|
sendRef.current = sendUserMessage;
|
|
243
257
|
}, [sendUserMessage]);
|
|
@@ -275,6 +289,38 @@ export function useChatStream(options) {
|
|
|
275
289
|
}
|
|
276
290
|
return ok;
|
|
277
291
|
}, [options.transport]);
|
|
292
|
+
const steerOrFollowUpUserMessage = useCallback(async (content) => {
|
|
293
|
+
const input = { content };
|
|
294
|
+
// No turn running: there is nothing to inject into and nothing whose
|
|
295
|
+
// `finally` would drain the queue — a normal send is the only route
|
|
296
|
+
// that delivers the message.
|
|
297
|
+
if (!isStreamingRef.current) {
|
|
298
|
+
void sendRef.current?.(input);
|
|
299
|
+
return 'sent';
|
|
300
|
+
}
|
|
301
|
+
const steer = options.transport.steer;
|
|
302
|
+
if (steer) {
|
|
303
|
+
const ok = await steer(content);
|
|
304
|
+
if (ok) {
|
|
305
|
+
dispatch({ type: 'append', message: newUserMessage(content) });
|
|
306
|
+
return 'steered';
|
|
307
|
+
}
|
|
308
|
+
// The turn may have ended while the steer was in flight; its
|
|
309
|
+
// `finally` already ran the queue drain, so a queued message would
|
|
310
|
+
// sit unsent until an unrelated future turn ends. Send it as a fresh
|
|
311
|
+
// turn instead. If a queued follow-up already started the next turn,
|
|
312
|
+
// isStreamingRef is true again and queueing below is correct.
|
|
313
|
+
if (!isStreamingRef.current) {
|
|
314
|
+
void sendRef.current?.(input);
|
|
315
|
+
return 'sent';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Turn still running but not steerable (transport lacks `steer`, or
|
|
319
|
+
// the loop rejected the injection): W6-2 queue, drained at turn end.
|
|
320
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
321
|
+
bumpFollowUpVersion();
|
|
322
|
+
return 'queued';
|
|
323
|
+
}, [options.transport]);
|
|
278
324
|
const setMessages = useCallback((messages) => {
|
|
279
325
|
dispatch({ type: 'reset', messages });
|
|
280
326
|
}, []);
|
|
@@ -293,7 +339,9 @@ export function useChatStream(options) {
|
|
|
293
339
|
messages: state.messages,
|
|
294
340
|
isStreaming: isStreamingRef.current,
|
|
295
341
|
sendUserMessage,
|
|
342
|
+
resumeTurn,
|
|
296
343
|
steerUserMessage,
|
|
344
|
+
steerOrFollowUpUserMessage,
|
|
297
345
|
followUpUserMessage,
|
|
298
346
|
// followUpVersion re-keys this memo so queue changes surface to the UI.
|
|
299
347
|
pendingFollowUps: followUpsRef.current,
|
|
@@ -306,7 +354,9 @@ export function useChatStream(options) {
|
|
|
306
354
|
[
|
|
307
355
|
state.messages,
|
|
308
356
|
sendUserMessage,
|
|
357
|
+
resumeTurn,
|
|
309
358
|
steerUserMessage,
|
|
359
|
+
steerOrFollowUpUserMessage,
|
|
310
360
|
followUpUserMessage,
|
|
311
361
|
removeFollowUp,
|
|
312
362
|
cancel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Steerable framework Tier 4 — headless React hooks (useAgentSession, useChatStream, useToolCallStatus) and Tailwind-themed components (ChatPanel, MessageList, OrchestrationPlanCard, ToolCallRenderer, SSEStreamView) for embedding Steerable agents in any React app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/storybook/",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"README.md"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@steerable/agent-protocol": "0.
|
|
60
|
+
"@steerable/agent-protocol": "0.4.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": ">=18.0.0",
|
package/src/hooks/index.ts
CHANGED
|
@@ -33,6 +33,12 @@ const {
|
|
|
33
33
|
messages, // ChatMessage[]
|
|
34
34
|
isStreaming, // boolean
|
|
35
35
|
sendUserMessage, // ({ content }) => Promise<void>
|
|
36
|
+
resumeTurn, // ({ metadata? }?) => Promise<void> — W7-1 crash resume
|
|
37
|
+
steerUserMessage, // (content) => Promise<boolean> — raw mid-turn steer
|
|
38
|
+
steerOrFollowUpUserMessage, // (content) => Promise<SteerOutcome> — steer with never-drop fallback
|
|
39
|
+
followUpUserMessage, // ({ content }) => void — queue for after the turn (W6-2)
|
|
40
|
+
pendingFollowUps, // ChatStreamSendInput[] — queued, not yet sent
|
|
41
|
+
removeFollowUp, // (index) => void — withdraw a queued message
|
|
36
42
|
cancel, // () => void
|
|
37
43
|
setMessages, // (messages) => void
|
|
38
44
|
appendMessage, // (message) => void
|
|
@@ -113,6 +119,19 @@ const { ... } = useChatStream({
|
|
|
113
119
|
});
|
|
114
120
|
```
|
|
115
121
|
|
|
122
|
+
## Steering and follow-ups (W6-2)
|
|
123
|
+
|
|
124
|
+
While a turn is streaming there are three ways to submit more input, with different delivery guarantees:
|
|
125
|
+
|
|
126
|
+
* `steerUserMessage(content)` — raw mid-turn steer. Resolves `true` when the running turn accepted the message (it is appended to the transcript at once); `false` when no steerable turn is active (no transport `steer`, or the turn already ended). On `false` nothing else happens — what to do with the draft is the caller's problem.
|
|
127
|
+
* `followUpUserMessage(input)` — queue the message; it is sent automatically as the next turn when the current turn's `finally` drains the queue. `pendingFollowUps` exposes the queue (in order, not yet sent) and `removeFollowUp(index)` withdraws one. `sendUserMessage` while streaming degrades into this same queue rather than dropping.
|
|
128
|
+
* `steerOrFollowUpUserMessage(content)` — the never-drop combinator, intended for composer Enter handlers. It tries the steer first and resolves a `SteerOutcome` telling the caller where the message landed:
|
|
129
|
+
* `'steered'` — injected into the running turn.
|
|
130
|
+
* `'queued'` — the turn is still running but not steerable; the message sits in the follow-up queue and auto-sends at turn end.
|
|
131
|
+
* `'sent'` — no turn was running by the time the steer attempt settled. This covers the race where the turn ends while `transport.steer` is in flight: that turn's `finally` already drained the queue, so queueing would strand the message until some unrelated future turn ends — it is sent immediately as a normal new turn instead.
|
|
132
|
+
|
|
133
|
+
The hook exposes only the neutral outcome; rendering any notice ("queued instead of injected", "N queued will be discarded") is the caller's job, in the caller's locale.
|
|
134
|
+
|
|
116
135
|
## Cancellation
|
|
117
136
|
|
|
118
137
|
`transport.stream` may return a `() => void` cancel handle. The hook stores it and calls it when:
|
|
@@ -122,8 +141,11 @@ const { ... } = useChatStream({
|
|
|
122
141
|
|
|
123
142
|
If your transport can't cancel, return `void` — the hook will simply mark `isStreaming` as `false` when the promise settles.
|
|
124
143
|
|
|
144
|
+
`cancel()` also drops every queued follow-up (W6-2): an explicit stop means "don't keep going", so queued messages are not auto-sent afterwards. If your UI shows `pendingFollowUps`, tell the user that stopping discards them before they confirm.
|
|
145
|
+
|
|
125
146
|
## Caveats
|
|
126
147
|
|
|
127
|
-
* `sendUserMessage`
|
|
148
|
+
* `sendUserMessage` while `isStreaming` is true does NOT drop the message — it appends to the W6-2 follow-up queue and auto-sends as the next turn. If you'd rather block input during streaming, wire your composer's send button to `disabled={isStreaming}`.
|
|
149
|
+
* `resumeTurn` (W7-1) streams an assistant turn WITHOUT appending a user message — the transport receives `{ content: '', metadata: { resume: true } }` and the backend replays the durable record's projection as the loop seed. Use it for a "continue the interrupted turn" affordance after a crash/kill; it is a no-op while streaming and is never queued as a follow-up.
|
|
128
150
|
* Tool results are attached to the **latest assistant message**. If you need per-call lookup, override `renderToolCall` on `ChatPanel` and pull the result from your own map.
|
|
129
151
|
* Setting `toolResultToMessage` to a non-null returner switches to a "tool result lives in its own message" model — useful if you want to render tool results as standalone bubbles. Otherwise the result rides inline on the assistant turn.
|
|
@@ -14,13 +14,14 @@ import {
|
|
|
14
14
|
useChatStream,
|
|
15
15
|
type ChatStreamSendInput,
|
|
16
16
|
type ChatStreamTransport,
|
|
17
|
+
type SteerOutcome,
|
|
17
18
|
} from './useChatStream';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* A transport whose streams stay open until the test explicitly finishes them,
|
|
21
22
|
* so we can submit follow-ups mid-stream and watch the queue drain.
|
|
22
23
|
*/
|
|
23
|
-
function makeControllableTransport() {
|
|
24
|
+
function makeControllableTransport(steer?: ChatStreamTransport['steer']) {
|
|
24
25
|
const streams: Array<{
|
|
25
26
|
input: ChatStreamSendInput;
|
|
26
27
|
onEvent: (e: SSEEvent) => void;
|
|
@@ -38,10 +39,20 @@ function makeControllableTransport() {
|
|
|
38
39
|
},
|
|
39
40
|
});
|
|
40
41
|
}),
|
|
42
|
+
...(steer ? { steer } : {}),
|
|
41
43
|
};
|
|
42
44
|
return { transport, streams };
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/** Manually-resolved promise, for parking a steer attempt mid-flight. */
|
|
48
|
+
function makeDeferred<T>() {
|
|
49
|
+
let resolve!: (value: T) => void;
|
|
50
|
+
const promise = new Promise<T>((r) => {
|
|
51
|
+
resolve = r;
|
|
52
|
+
});
|
|
53
|
+
return { promise, resolve };
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
function makeTransport(
|
|
46
57
|
script: SSEEvent[][],
|
|
47
58
|
): { transport: ChatStreamTransport; cancel: ReturnType<typeof vi.fn> } {
|
|
@@ -337,6 +348,148 @@ describe('steerUserMessage', () => {
|
|
|
337
348
|
});
|
|
338
349
|
});
|
|
339
350
|
|
|
351
|
+
describe('steerOrFollowUpUserMessage', () => {
|
|
352
|
+
it("resolves 'steered' and appends the message when the turn accepts the injection", async () => {
|
|
353
|
+
const steer = vi.fn().mockResolvedValue(true);
|
|
354
|
+
const { transport, streams } = makeControllableTransport(steer);
|
|
355
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
356
|
+
|
|
357
|
+
act(() => {
|
|
358
|
+
void result.current.sendUserMessage({ content: 'start' });
|
|
359
|
+
});
|
|
360
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
361
|
+
|
|
362
|
+
let outcome: SteerOutcome | undefined;
|
|
363
|
+
await act(async () => {
|
|
364
|
+
outcome = await result.current.steerOrFollowUpUserMessage('补充一句');
|
|
365
|
+
});
|
|
366
|
+
expect(outcome).toBe('steered');
|
|
367
|
+
expect(
|
|
368
|
+
result.current.messages.some((m) => m.role === 'user' && m.content === '补充一句'),
|
|
369
|
+
).toBe(true);
|
|
370
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
371
|
+
expect(streams).toHaveLength(1);
|
|
372
|
+
|
|
373
|
+
await act(async () => {
|
|
374
|
+
streams[0]!.finish();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("resolves 'queued' when the transport has no steer, then drains at turn end", async () => {
|
|
379
|
+
const { transport, streams } = makeControllableTransport();
|
|
380
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
381
|
+
|
|
382
|
+
act(() => {
|
|
383
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
384
|
+
});
|
|
385
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
386
|
+
|
|
387
|
+
let outcome: SteerOutcome | undefined;
|
|
388
|
+
await act(async () => {
|
|
389
|
+
outcome = await result.current.steerOrFollowUpUserMessage('排队等下轮');
|
|
390
|
+
});
|
|
391
|
+
expect(outcome).toBe('queued');
|
|
392
|
+
expect(result.current.pendingFollowUps).toEqual([{ content: '排队等下轮' }]);
|
|
393
|
+
|
|
394
|
+
await act(async () => {
|
|
395
|
+
streams[0]!.finish();
|
|
396
|
+
});
|
|
397
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
398
|
+
expect(streams[1]!.input.content).toBe('排队等下轮');
|
|
399
|
+
await act(async () => {
|
|
400
|
+
streams[1]!.finish();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("resolves 'queued' when the steer is rejected and the turn is still running", async () => {
|
|
405
|
+
const steer = vi.fn().mockResolvedValue(false);
|
|
406
|
+
const { transport, streams } = makeControllableTransport(steer);
|
|
407
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
408
|
+
|
|
409
|
+
act(() => {
|
|
410
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
411
|
+
});
|
|
412
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
413
|
+
|
|
414
|
+
let outcome: SteerOutcome | undefined;
|
|
415
|
+
await act(async () => {
|
|
416
|
+
outcome = await result.current.steerOrFollowUpUserMessage('拒收的转向');
|
|
417
|
+
});
|
|
418
|
+
expect(outcome).toBe('queued');
|
|
419
|
+
expect(steer).toHaveBeenCalledWith('拒收的转向');
|
|
420
|
+
expect(result.current.pendingFollowUps).toEqual([{ content: '拒收的转向' }]);
|
|
421
|
+
|
|
422
|
+
await act(async () => {
|
|
423
|
+
streams[0]!.finish();
|
|
424
|
+
});
|
|
425
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
426
|
+
expect(streams[1]!.input.content).toBe('拒收的转向');
|
|
427
|
+
await act(async () => {
|
|
428
|
+
streams[1]!.finish();
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("resolves 'sent' when the turn ended while the steer attempt was in flight", async () => {
|
|
433
|
+
const gate = makeDeferred<boolean>();
|
|
434
|
+
const steer = vi.fn(() => gate.promise);
|
|
435
|
+
const { transport, streams } = makeControllableTransport(steer);
|
|
436
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
437
|
+
|
|
438
|
+
act(() => {
|
|
439
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
440
|
+
});
|
|
441
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
442
|
+
|
|
443
|
+
let outcomePromise: Promise<SteerOutcome>;
|
|
444
|
+
await act(async () => {
|
|
445
|
+
outcomePromise = result.current.steerOrFollowUpUserMessage('来迟的补充');
|
|
446
|
+
});
|
|
447
|
+
expect(steer).toHaveBeenCalledWith('来迟的补充');
|
|
448
|
+
|
|
449
|
+
// The turn ends while the steer is still pending: its `finally` drains
|
|
450
|
+
// the (empty) queue and clears isStreaming before the steer settles.
|
|
451
|
+
await act(async () => {
|
|
452
|
+
streams[0]!.finish();
|
|
453
|
+
});
|
|
454
|
+
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
455
|
+
|
|
456
|
+
let outcome: SteerOutcome | undefined;
|
|
457
|
+
await act(async () => {
|
|
458
|
+
gate.resolve(false);
|
|
459
|
+
outcome = await outcomePromise;
|
|
460
|
+
});
|
|
461
|
+
// Queueing here would strand the message (the drain already ran), so it
|
|
462
|
+
// goes out immediately as a fresh turn instead.
|
|
463
|
+
expect(outcome).toBe('sent');
|
|
464
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
465
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
466
|
+
expect(streams[1]!.input.content).toBe('来迟的补充');
|
|
467
|
+
await act(async () => {
|
|
468
|
+
streams[1]!.finish();
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("resolves 'sent' without touching steer when no turn is streaming", async () => {
|
|
473
|
+
const steer = vi.fn().mockResolvedValue(true);
|
|
474
|
+
const { transport, streams } = makeControllableTransport(steer);
|
|
475
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
476
|
+
|
|
477
|
+
let outcome: SteerOutcome | undefined;
|
|
478
|
+
await act(async () => {
|
|
479
|
+
outcome = await result.current.steerOrFollowUpUserMessage('直接发出');
|
|
480
|
+
});
|
|
481
|
+
expect(outcome).toBe('sent');
|
|
482
|
+
expect(steer).not.toHaveBeenCalled();
|
|
483
|
+
expect(streams).toHaveLength(1);
|
|
484
|
+
expect(streams[0]!.input.content).toBe('直接发出');
|
|
485
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
486
|
+
|
|
487
|
+
await act(async () => {
|
|
488
|
+
streams[0]!.finish();
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
340
493
|
describe('follow-up queue (W6-2)', () => {
|
|
341
494
|
it('sendUserMessage while streaming queues instead of dropping, then auto-sends next turn', async () => {
|
|
342
495
|
const { transport, streams } = makeControllableTransport();
|
|
@@ -462,3 +615,90 @@ describe('follow-up queue (W6-2)', () => {
|
|
|
462
615
|
expect(streams).toHaveLength(1);
|
|
463
616
|
});
|
|
464
617
|
});
|
|
618
|
+
|
|
619
|
+
describe('resumeTurn (W7-1)', () => {
|
|
620
|
+
it('streams an assistant turn with resume:true and NO new user message', async () => {
|
|
621
|
+
const { transport, streams } = makeControllableTransport();
|
|
622
|
+
const { result } = renderHook(() =>
|
|
623
|
+
useChatStream({
|
|
624
|
+
transport,
|
|
625
|
+
initialMessages: [
|
|
626
|
+
{
|
|
627
|
+
id: 'u1',
|
|
628
|
+
role: 'user',
|
|
629
|
+
content: '被中断的请求',
|
|
630
|
+
createdAt: new Date().toISOString(),
|
|
631
|
+
},
|
|
632
|
+
],
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
act(() => {
|
|
637
|
+
void result.current.resumeTurn({ metadata: { mode: 'plan' } });
|
|
638
|
+
});
|
|
639
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
640
|
+
await act(async () => {
|
|
641
|
+
streams[0]!.finish();
|
|
642
|
+
});
|
|
643
|
+
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
644
|
+
|
|
645
|
+
// The wire input carries the resume marker and the caller's metadata,
|
|
646
|
+
// with empty content — the backend replays the record, so no user text
|
|
647
|
+
// is re-sent.
|
|
648
|
+
expect(streams).toHaveLength(1);
|
|
649
|
+
expect(streams[0]!.input).toEqual({
|
|
650
|
+
content: '',
|
|
651
|
+
metadata: { mode: 'plan', resume: true },
|
|
652
|
+
});
|
|
653
|
+
// The transcript gains ONLY the assistant placeholder — the interrupted
|
|
654
|
+
// turn's user message is already there (initialMessages), not duplicated.
|
|
655
|
+
expect(result.current.messages.map((m) => m.role)).toEqual([
|
|
656
|
+
'user',
|
|
657
|
+
'assistant',
|
|
658
|
+
]);
|
|
659
|
+
expect(result.current.isStreaming).toBe(false);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('is a no-op while streaming (a resume is not queueable)', async () => {
|
|
663
|
+
const { transport, streams } = makeControllableTransport();
|
|
664
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
665
|
+
|
|
666
|
+
act(() => {
|
|
667
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
668
|
+
});
|
|
669
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
670
|
+
|
|
671
|
+
await act(async () => {
|
|
672
|
+
await result.current.resumeTurn();
|
|
673
|
+
});
|
|
674
|
+
expect(streams).toHaveLength(1);
|
|
675
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
676
|
+
|
|
677
|
+
await act(async () => {
|
|
678
|
+
streams[0]!.finish();
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('drains the follow-up queue after the resumed turn ends', async () => {
|
|
683
|
+
const { transport, streams } = makeControllableTransport();
|
|
684
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
685
|
+
|
|
686
|
+
act(() => {
|
|
687
|
+
void result.current.resumeTurn();
|
|
688
|
+
});
|
|
689
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
690
|
+
|
|
691
|
+
await act(async () => {
|
|
692
|
+
result.current.followUpUserMessage({ content: 'next question' });
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
await act(async () => {
|
|
696
|
+
streams[0]!.finish();
|
|
697
|
+
});
|
|
698
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
699
|
+
expect(streams[1]!.input.content).toBe('next question');
|
|
700
|
+
await act(async () => {
|
|
701
|
+
streams[1]!.finish();
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
});
|
|
@@ -48,6 +48,18 @@ export interface ChatStreamTransport {
|
|
|
48
48
|
steer?: (content: string) => Promise<boolean>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Where `steerOrFollowUpUserMessage` actually delivered the message:
|
|
53
|
+
* - `'steered'` — the running turn accepted the injection.
|
|
54
|
+
* - `'queued'` — the turn is still running but not steerable; the message
|
|
55
|
+
* sits in the W6-2 follow-up queue and auto-sends at turn end.
|
|
56
|
+
* - `'sent'` — no turn was running by the time the steer attempt settled;
|
|
57
|
+
* the message went out immediately as a normal new turn.
|
|
58
|
+
* The union lets callers render their own locale-specific notice per
|
|
59
|
+
* outcome; the hook itself carries no user-facing copy.
|
|
60
|
+
*/
|
|
61
|
+
export type SteerOutcome = 'steered' | 'queued' | 'sent';
|
|
62
|
+
|
|
51
63
|
export interface UseChatStreamOptions {
|
|
52
64
|
/** Required transport handle; see `ChatStreamTransport`. */
|
|
53
65
|
transport: ChatStreamTransport;
|
|
@@ -76,6 +88,15 @@ export interface UseChatStreamReturn {
|
|
|
76
88
|
* sent automatically as the next turn when the current one ends.
|
|
77
89
|
*/
|
|
78
90
|
sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* W7-1: resume the interrupted turn recorded on the backend. Streams an
|
|
93
|
+
* assistant turn WITHOUT appending a user message — the transport receives
|
|
94
|
+
* `{ content: '', metadata: { ...input?.metadata, resume: true } }` and the
|
|
95
|
+
* backend replays the durable record's projection as the loop seed, so the
|
|
96
|
+
* user never retypes the interrupted request. No-op while streaming (a
|
|
97
|
+
* resume is not queueable as a follow-up — it is not a user message).
|
|
98
|
+
*/
|
|
99
|
+
resumeTurn: (input?: { metadata?: Record<string, unknown> }) => Promise<void>;
|
|
79
100
|
/**
|
|
80
101
|
* Steer the running turn with an extra user message. No-op resolving
|
|
81
102
|
* `false` when not streaming or the transport has no `steer`. On success
|
|
@@ -83,6 +104,16 @@ export interface UseChatStreamReturn {
|
|
|
83
104
|
* consumes it at the next round boundary).
|
|
84
105
|
*/
|
|
85
106
|
steerUserMessage: (content: string) => Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* Steer with a never-drop fallback (W6-2): tries `steerUserMessage` first,
|
|
109
|
+
* then degrades based on live turn state — see `SteerOutcome` for the
|
|
110
|
+
* three outcomes. The turn-already-ended race is resolved by re-checking
|
|
111
|
+
* `isStreaming` after the steer attempt settles: a finished turn's
|
|
112
|
+
* `finally` has already drained the queue, so queueing there would strand
|
|
113
|
+
* the message until some future turn ends — it is sent directly instead.
|
|
114
|
+
* Prefer this over `steerUserMessage` for composer Enter handlers.
|
|
115
|
+
*/
|
|
116
|
+
steerOrFollowUpUserMessage: (content: string) => Promise<SteerOutcome>;
|
|
86
117
|
/**
|
|
87
118
|
* Explicitly queue a message as a follow-up (W6-2): sent automatically as
|
|
88
119
|
* the next turn once the current turn finishes. When not streaming this is
|
|
@@ -313,16 +344,15 @@ export function useChatStream(
|
|
|
313
344
|
[options],
|
|
314
345
|
);
|
|
315
346
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
347
|
+
// Shared turn core: stream one assistant turn and drain the follow-up
|
|
348
|
+
// queue at the end. `echoUserMessage` is false only for W7-1 resume —
|
|
349
|
+
// the interrupted turn's user message is already in the transcript and
|
|
350
|
+
// the backend replays it from the durable record.
|
|
351
|
+
const startTurn = useCallback(
|
|
352
|
+
async (input: ChatStreamSendInput, opts: { echoUserMessage: boolean }) => {
|
|
353
|
+
if (opts.echoUserMessage) {
|
|
354
|
+
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
324
355
|
}
|
|
325
|
-
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
326
356
|
dispatch({ type: 'append', message: newAssistantPlaceholder() });
|
|
327
357
|
setStreaming(true);
|
|
328
358
|
try {
|
|
@@ -353,6 +383,31 @@ export function useChatStream(
|
|
|
353
383
|
[handleEvent, options.transport, setStreaming],
|
|
354
384
|
);
|
|
355
385
|
|
|
386
|
+
const sendUserMessage = useCallback(
|
|
387
|
+
async (input: ChatStreamSendInput) => {
|
|
388
|
+
if (isStreamingRef.current) {
|
|
389
|
+
// W6-2: queue instead of dropping. The finishing turn's `finally`
|
|
390
|
+
// drains the queue and starts the next turn automatically.
|
|
391
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
392
|
+
bumpFollowUpVersion();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
await startTurn(input, { echoUserMessage: true });
|
|
396
|
+
},
|
|
397
|
+
[startTurn],
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const resumeTurn = useCallback(
|
|
401
|
+
async (input?: { metadata?: Record<string, unknown> }) => {
|
|
402
|
+
if (isStreamingRef.current) return;
|
|
403
|
+
await startTurn(
|
|
404
|
+
{ content: '', metadata: { ...input?.metadata, resume: true } },
|
|
405
|
+
{ echoUserMessage: false },
|
|
406
|
+
);
|
|
407
|
+
},
|
|
408
|
+
[startTurn],
|
|
409
|
+
);
|
|
410
|
+
|
|
356
411
|
useEffect(() => {
|
|
357
412
|
sendRef.current = sendUserMessage;
|
|
358
413
|
}, [sendUserMessage]);
|
|
@@ -398,6 +453,42 @@ export function useChatStream(
|
|
|
398
453
|
[options.transport],
|
|
399
454
|
);
|
|
400
455
|
|
|
456
|
+
const steerOrFollowUpUserMessage = useCallback(
|
|
457
|
+
async (content: string): Promise<SteerOutcome> => {
|
|
458
|
+
const input: ChatStreamSendInput = { content };
|
|
459
|
+
// No turn running: there is nothing to inject into and nothing whose
|
|
460
|
+
// `finally` would drain the queue — a normal send is the only route
|
|
461
|
+
// that delivers the message.
|
|
462
|
+
if (!isStreamingRef.current) {
|
|
463
|
+
void sendRef.current?.(input);
|
|
464
|
+
return 'sent';
|
|
465
|
+
}
|
|
466
|
+
const steer = options.transport.steer;
|
|
467
|
+
if (steer) {
|
|
468
|
+
const ok = await steer(content);
|
|
469
|
+
if (ok) {
|
|
470
|
+
dispatch({ type: 'append', message: newUserMessage(content) });
|
|
471
|
+
return 'steered';
|
|
472
|
+
}
|
|
473
|
+
// The turn may have ended while the steer was in flight; its
|
|
474
|
+
// `finally` already ran the queue drain, so a queued message would
|
|
475
|
+
// sit unsent until an unrelated future turn ends. Send it as a fresh
|
|
476
|
+
// turn instead. If a queued follow-up already started the next turn,
|
|
477
|
+
// isStreamingRef is true again and queueing below is correct.
|
|
478
|
+
if (!isStreamingRef.current) {
|
|
479
|
+
void sendRef.current?.(input);
|
|
480
|
+
return 'sent';
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Turn still running but not steerable (transport lacks `steer`, or
|
|
484
|
+
// the loop rejected the injection): W6-2 queue, drained at turn end.
|
|
485
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
486
|
+
bumpFollowUpVersion();
|
|
487
|
+
return 'queued';
|
|
488
|
+
},
|
|
489
|
+
[options.transport],
|
|
490
|
+
);
|
|
491
|
+
|
|
401
492
|
const setMessages = useCallback((messages: ChatMessage[]) => {
|
|
402
493
|
dispatch({ type: 'reset', messages });
|
|
403
494
|
}, []);
|
|
@@ -420,7 +511,9 @@ export function useChatStream(
|
|
|
420
511
|
messages: state.messages,
|
|
421
512
|
isStreaming: isStreamingRef.current,
|
|
422
513
|
sendUserMessage,
|
|
514
|
+
resumeTurn,
|
|
423
515
|
steerUserMessage,
|
|
516
|
+
steerOrFollowUpUserMessage,
|
|
424
517
|
followUpUserMessage,
|
|
425
518
|
// followUpVersion re-keys this memo so queue changes surface to the UI.
|
|
426
519
|
pendingFollowUps: followUpsRef.current,
|
|
@@ -433,7 +526,9 @@ export function useChatStream(
|
|
|
433
526
|
[
|
|
434
527
|
state.messages,
|
|
435
528
|
sendUserMessage,
|
|
529
|
+
resumeTurn,
|
|
436
530
|
steerUserMessage,
|
|
531
|
+
steerOrFollowUpUserMessage,
|
|
437
532
|
followUpUserMessage,
|
|
438
533
|
removeFollowUp,
|
|
439
534
|
cancel,
|