@adminforth/agent 2.2.4 → 2.2.5

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.
@@ -35,7 +35,21 @@ export function createAgentSteerQueue({
35
35
  queue.value = queue.value.filter((item: QueuedMessage) => item.id !== id);
36
36
  }
37
37
 
38
- function injectSteerMessage(text: string) {
38
+ // Break the assistant's live stream at the steer point: freeze whatever streamed so far
39
+ // (e.g. the current reasoning block), drop the steer message in, and let the rest stream
40
+ // into a fresh assistant message after it — so the conversation reads
41
+ // "reasoning block → steer → new reasoning block" instead of the steer landing next to
42
+ // the user's original prompt.
43
+ //
44
+ // This reaches into the AI SDK's in-flight streaming state (`chat.activeResponse`):
45
+ // - reasoning/text parts are looked up by id reference in `active*Parts`, so pointing
46
+ // those entries at fresh (empty) parts redirects subsequent deltas past the steer;
47
+ // - the stream writer pushes a *new* message whenever `state.message.id` no longer
48
+ // matches the last displayed message, so swapping in a new `message` splits the bubble
49
+ // without duplicating the frozen block.
50
+ // If that internal shape isn't present (not streaming, or a future SDK change), we fall
51
+ // back to inserting the steer just above the streaming assistant.
52
+ async function injectSteerMessage(text: string) {
39
53
  const chat = currentChat.value;
40
54
  if (!chat) {
41
55
  return;
@@ -46,6 +60,11 @@ export function createAgentSteerQueue({
46
60
  parts: [{ type: 'text', text, state: 'done' }],
47
61
  };
48
62
  const messages = chat.messages;
63
+
64
+ if (breakStreamWithSteer(chat, messages, steerMessage)) {
65
+ return;
66
+ }
67
+
49
68
  const lastMessage = messages[messages.length - 1];
50
69
  if (lastMessage?.role === 'assistant') {
51
70
  messages.splice(messages.length - 1, 0, steerMessage);
@@ -54,6 +73,58 @@ export function createAgentSteerQueue({
54
73
  }
55
74
  }
56
75
 
76
+ function breakStreamWithSteer(chat: Chat<any>, messages: any[], steerMessage: IMessage): boolean {
77
+ try {
78
+ const streamingState = (chat as any).activeResponse?.state;
79
+ const streamingMessage = streamingState?.message;
80
+ const lastIndex = messages.length - 1;
81
+ const lastMessage = messages[lastIndex];
82
+ const isStreamingIntoLastAssistant =
83
+ streamingMessage?.role === 'assistant'
84
+ && Array.isArray(streamingMessage.parts)
85
+ && streamingMessage.parts.length > 0
86
+ && lastMessage?.role === 'assistant';
87
+
88
+ if (!isStreamingIntoLastAssistant) {
89
+ return false;
90
+ }
91
+
92
+ // 1) Freeze block 1 as a fresh, decoupled assistant message. Copying the parts (and
93
+ // reassigning the array slot) detaches it from the SDK's live parts and forces a
94
+ // re-render so the block stops showing as "streaming".
95
+ const frozenParts = streamingMessage.parts.map((part: any) => ({
96
+ ...part,
97
+ state: part?.state === 'streaming' ? 'done' : part?.state,
98
+ }));
99
+ messages[lastIndex] = { ...streamingMessage, parts: frozenParts };
100
+ console.log('Frozen parts:', frozenParts);
101
+ // 2) Redirect the SDK's in-flight reasoning/text parts into a fresh continuation
102
+ // message so subsequent deltas (resolved by id reference) land after the steer.
103
+ const continuation: any = { id: crypto.randomUUID(), role: 'assistant', parts: [] };
104
+ const redirectActiveParts = (activeParts: Record<string, any> | undefined) => {
105
+ if (!activeParts) {
106
+ return;
107
+ }
108
+ for (const id of Object.keys(activeParts)) {
109
+ const continued = { ...activeParts[id], text: '', state: 'streaming' };
110
+ activeParts[id] = continued;
111
+ continuation.parts.push(continued);
112
+ }
113
+ };
114
+ redirectActiveParts(streamingState.activeReasoningParts);
115
+ redirectActiveParts(streamingState.activeTextParts);
116
+
117
+ // 3) Hand the SDK the continuation as its streaming message; its next write() pushes
118
+ // it as a new assistant bubble. Slot the steer between the frozen block and it.
119
+ streamingState.message = continuation;
120
+ messages.push(steerMessage);
121
+ return true;
122
+ } catch (error) {
123
+ console.error('Steer stream-break failed, inserting steer inline instead', error);
124
+ return false;
125
+ }
126
+ }
127
+
57
128
  // Persist the steer by appending it onto the running turn's prompt (display only — the
58
129
  // LLM already has it via the running turn + checkpointer, so this never re-enters the
59
130
  // model context). The backend adds the marker; the reload path splits it back out.
@@ -18,7 +18,7 @@
18
18
  "@shikijs/langs": "^4.0.2",
19
19
  "@shikijs/themes": "^4.0.2",
20
20
  "@vueuse/core": "^14.2.1",
21
- "ai": "^6.0.168",
21
+ "ai": "6.0.168",
22
22
  "custom-vue-scrollbar": "^0.0.8",
23
23
  "dompurify": "^3.3.3",
24
24
  "katex": "^0.16.45",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",