likec4 1.59.3 → 1.60.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/__app__/codegen/react.mjs +6 -6
- package/__app__/codegen/webcomponent.mjs +147 -69
- package/__app__/src/aichat/index.mjs +10 -5
- package/__app__/src/chunks/AIChat.mjs +1134 -693
- package/__app__/src/chunks/LikeC4Styles.mjs +4101 -1737
- package/__app__/src/chunks/ProjectsOverview.mjs +624 -334
- package/__app__/src/chunks/es.mjs +515 -335
- package/__app__/src/chunks/hooks.mjs +578 -366
- package/__app__/src/chunks/libs/@floating-ui.mjs +2720 -1966
- package/__app__/src/chunks/libs/@mantine.mjs +14067 -9487
- package/__app__/src/chunks/libs/@tanstack-ai.mjs +2266 -1146
- package/__app__/src/chunks/libs/@tanstack.mjs +3152 -1932
- package/__app__/src/chunks/libs/@xstate/react.mjs +2924 -1468
- package/__app__/src/chunks/libs/@xyflow/react.mjs +6627 -4067
- package/__app__/src/chunks/libs/bezier-js.mjs +1312 -726
- package/__app__/src/chunks/libs/d3.mjs +3218 -1509
- package/__app__/src/chunks/libs/fast-equals.mjs +437 -201
- package/__app__/src/chunks/libs/framer.mjs +9251 -3846
- package/__app__/src/chunks/libs/nanostores.mjs +174 -0
- package/__app__/src/chunks/libs/zod.mjs +4591 -2363
- package/__app__/src/chunks/rolldown-runtime.mjs +39 -23
- package/__app__/src/chunks/styled-system.mjs +3538 -756
- package/__app__/src/fonts.css +112 -1
- package/__app__/src/main.mjs +44398 -24039
- package/__app__/src/style.css +18488 -1
- package/config/schema.json +136 -97
- package/dist/app/render-view-client.css +1 -1
- package/dist/app/render-view-client.js +506 -338
- package/dist/chunks/enableServer.mjs +1 -1
- package/dist/chunks/filenames.mjs +16 -14
- package/dist/chunks/generate-likec4.mjs +5 -0
- package/dist/chunks/index.d.mts +246 -180
- package/dist/chunks/index2.d.mts +2882 -1977
- package/dist/chunks/libs/@chevrotain/gast.mjs +27 -1
- package/dist/chunks/libs/@chevrotain/regexp-to-ast.mjs +81 -9
- package/dist/chunks/libs/@chevrotain/types.d.mts +111 -0
- package/dist/chunks/libs/@logtape/logtape.mjs +5 -4
- package/dist/chunks/libs/@lume/kiwi.mjs +1 -1
- package/dist/chunks/libs/@ts-graphviz/core.mjs +1 -1
- package/dist/chunks/libs/birpc.mjs +1 -1
- package/dist/chunks/libs/chevrotain-allstar.mjs +2 -2
- package/dist/chunks/libs/chevrotain.mjs +87 -35
- package/dist/chunks/libs/eventemitter3.mjs +1 -1
- package/dist/chunks/libs/json5.mjs +4 -4
- package/dist/chunks/libs/langium.d.mts +9903 -5297
- package/dist/chunks/libs/langium.mjs +16 -24
- package/dist/chunks/libs/merge-error-cause.mjs +1 -1
- package/dist/chunks/libs/p-limit.mjs +1 -0
- package/dist/chunks/libs/p-queue.mjs +1 -1
- package/dist/chunks/libs/package-manager-detector.mjs +1 -1
- package/dist/chunks/libs/pako.mjs +2 -2
- package/dist/chunks/libs/pretty-ms.mjs +1 -1
- package/dist/chunks/libs/remeda.mjs +1 -1
- package/dist/chunks/libs/ufo.mjs +1 -1
- package/dist/chunks/libs/unstorage.mjs +1 -1
- package/dist/chunks/libs/which.mjs +1 -1
- package/dist/chunks/node.mjs +60 -60
- package/dist/chunks/rolldown-runtime.mjs +1 -1
- package/dist/chunks/sequence-view.mjs +1 -1
- package/dist/chunks/vite-plugin.mjs +55 -52
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.mjs +208 -1300
- package/dist/config/index.d.mts +2 -2
- package/dist/generators/likec4.d.mts +6578 -0
- package/dist/generators/likec4.mjs +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/model/index.d.mts +8 -5
- package/dist/vite-plugin/index.d.mts +6 -3
- package/dist/vite-plugin/index.mjs +1 -1
- package/dist/vite-plugin/internal/chunks/rolldown-runtime.mjs +9 -0
- package/dist/vite-plugin/internal/index.d.mts +16 -15
- package/dist/vite-plugin/internal/index.mjs +144 -46
- package/generators/likec4/package.json +4 -0
- package/package.json +117 -110
- package/react/index.d.mts +8500 -5399
- package/react/index.mjs +534 -58617
- package/react/rolldown-runtime-DnkiqaRY.js +1 -0
- package/skills/likec4-dsl/SKILL.md +428 -0
- package/skills/likec4-dsl/evals/evals-public.json +202 -0
- package/skills/likec4-dsl/evals/grading-spec.json +589 -0
- package/skills/likec4-dsl/references/bridge-leanix-drawio.md +42 -0
- package/skills/likec4-dsl/references/cli.md +139 -0
- package/skills/likec4-dsl/references/configuration.md +266 -0
- package/skills/likec4-dsl/references/deployment.md +160 -0
- package/skills/likec4-dsl/references/dynamic-views.md +455 -0
- package/skills/likec4-dsl/references/examples.md +370 -0
- package/skills/likec4-dsl/references/identifier-validity.md +90 -0
- package/skills/likec4-dsl/references/include-predicates-wildcards.md +179 -0
- package/skills/likec4-dsl/references/model.md +151 -0
- package/skills/likec4-dsl/references/predicates.md +123 -0
- package/skills/likec4-dsl/references/relationships-bidirectional.md +176 -0
- package/skills/likec4-dsl/references/specification.md +75 -0
- package/skills/likec4-dsl/references/style-tokens-colors.md +150 -0
- package/skills/likec4-dsl/references/troubleshooting.md +96 -0
- package/skills/likec4-dsl/references/views.md +329 -0
- package/vite-plugin-modules.d.ts +4 -2
- package/dist/chunks/libs/picomatch.mjs +0 -1
- package/dist/chunks/libs/zod.d.mts +0 -2140
- package/dist/chunks/libs/zod.mjs +0 -65
|
@@ -1,152 +1,234 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { createContext
|
|
3
|
-
import { Fragment as
|
|
1
|
+
import { t as __commonJSMin } from "../rolldown-runtime.mjs";
|
|
2
|
+
import { createContext, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region ../../node_modules/.pnpm/@tanstack+devtools-event-client@0.4.3/node_modules/@tanstack/devtools-event-client/dist/esm/plugin.js
|
|
5
5
|
var EventClient = class {
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
this.debugLog("Connected to event bus")
|
|
6
|
+
#enabled = true;
|
|
7
|
+
#pluginId;
|
|
8
|
+
#eventTarget;
|
|
9
|
+
#debug;
|
|
10
|
+
#queuedEvents;
|
|
11
|
+
#connected;
|
|
12
|
+
#connectIntervalId;
|
|
13
|
+
#connectEveryMs;
|
|
14
|
+
#retryCount = 0;
|
|
15
|
+
#maxRetries = 5;
|
|
16
|
+
#connecting = false;
|
|
17
|
+
#failedToConnect = false;
|
|
18
|
+
#internalEventTarget = null;
|
|
19
|
+
#onConnected = () => {
|
|
20
|
+
this.debugLog("Connected to event bus");
|
|
21
|
+
this.#connected = true;
|
|
22
|
+
this.#connecting = false;
|
|
23
|
+
this.debugLog("Emitting queued events", this.#queuedEvents);
|
|
24
|
+
this.#queuedEvents.forEach((event) => this.emitEventToBus(event));
|
|
25
|
+
this.#queuedEvents = [];
|
|
26
|
+
this.stopConnectLoop();
|
|
27
|
+
this.#eventTarget().removeEventListener("tanstack-connect-success", this.#onConnected);
|
|
21
28
|
};
|
|
22
|
-
#
|
|
23
|
-
if (this.#
|
|
24
|
-
this.#
|
|
29
|
+
#retryConnection = () => {
|
|
30
|
+
if (this.#retryCount < this.#maxRetries) {
|
|
31
|
+
this.#retryCount++;
|
|
32
|
+
this.dispatchCustomEvent("tanstack-connect", {});
|
|
25
33
|
return;
|
|
26
34
|
}
|
|
27
|
-
this.#
|
|
35
|
+
this.#eventTarget().removeEventListener("tanstack-connect", this.#retryConnection);
|
|
36
|
+
this.#failedToConnect = true;
|
|
37
|
+
this.debugLog("Max retries reached, giving up on connection");
|
|
38
|
+
this.stopConnectLoop();
|
|
28
39
|
};
|
|
29
|
-
#
|
|
30
|
-
|
|
40
|
+
#connectFunction = () => {
|
|
41
|
+
if (this.#connecting) return;
|
|
42
|
+
this.#connecting = true;
|
|
43
|
+
this.#eventTarget().addEventListener("tanstack-connect-success", this.#onConnected);
|
|
44
|
+
this.#retryConnection();
|
|
31
45
|
};
|
|
32
|
-
constructor({ pluginId
|
|
33
|
-
this.#
|
|
46
|
+
constructor({ pluginId, debug = false, enabled = true, reconnectEveryMs = 300 }) {
|
|
47
|
+
this.#pluginId = pluginId;
|
|
48
|
+
this.#enabled = enabled;
|
|
49
|
+
this.#eventTarget = this.getGlobalTarget;
|
|
50
|
+
this.#debug = debug;
|
|
51
|
+
this.debugLog(" Initializing event subscription for plugin", this.#pluginId);
|
|
52
|
+
this.#queuedEvents = [];
|
|
53
|
+
this.#connected = false;
|
|
54
|
+
this.#failedToConnect = false;
|
|
55
|
+
this.#connectIntervalId = null;
|
|
56
|
+
this.#connectEveryMs = reconnectEveryMs;
|
|
34
57
|
}
|
|
35
58
|
startConnectLoop() {
|
|
36
|
-
this.#
|
|
59
|
+
if (this.#connectIntervalId !== null || this.#connected) return;
|
|
60
|
+
this.debugLog(`Starting connect loop (every ${this.#connectEveryMs}ms)`);
|
|
61
|
+
this.#connectIntervalId = setInterval(this.#retryConnection, this.#connectEveryMs);
|
|
37
62
|
}
|
|
38
63
|
stopConnectLoop() {
|
|
39
|
-
this.#
|
|
64
|
+
this.#connecting = false;
|
|
65
|
+
if (this.#connectIntervalId === null) return;
|
|
66
|
+
clearInterval(this.#connectIntervalId);
|
|
67
|
+
this.#connectIntervalId = null;
|
|
68
|
+
this.#queuedEvents = [];
|
|
69
|
+
this.debugLog("Stopped connect loop");
|
|
40
70
|
}
|
|
41
|
-
debugLog(...
|
|
42
|
-
this.#
|
|
71
|
+
debugLog(...args) {
|
|
72
|
+
if (this.#debug) console.log(`🌴 [tanstack-devtools:${this.#pluginId}-plugin]`, ...args);
|
|
43
73
|
}
|
|
44
74
|
getGlobalTarget() {
|
|
45
|
-
if (typeof globalThis
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
75
|
+
if (typeof globalThis !== "undefined" && globalThis.__TANSTACK_EVENT_TARGET__) {
|
|
76
|
+
this.debugLog("Using global event target");
|
|
77
|
+
return globalThis.__TANSTACK_EVENT_TARGET__;
|
|
78
|
+
}
|
|
79
|
+
if (typeof window !== "undefined" && typeof window.addEventListener !== "undefined") {
|
|
80
|
+
this.debugLog("Using window as event target");
|
|
81
|
+
return window;
|
|
82
|
+
}
|
|
83
|
+
const eventTarget = typeof EventTarget !== "undefined" ? new EventTarget() : void 0;
|
|
84
|
+
if (typeof eventTarget === "undefined" || typeof eventTarget.addEventListener === "undefined") {
|
|
85
|
+
this.debugLog("No event mechanism available, running in non-web environment");
|
|
86
|
+
return {
|
|
87
|
+
addEventListener: () => {},
|
|
88
|
+
removeEventListener: () => {},
|
|
89
|
+
dispatchEvent: () => false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
this.debugLog("Using new EventTarget as fallback");
|
|
93
|
+
return eventTarget;
|
|
53
94
|
}
|
|
54
95
|
getPluginId() {
|
|
55
|
-
return this.#
|
|
96
|
+
return this.#pluginId;
|
|
56
97
|
}
|
|
57
|
-
dispatchCustomEventShim(
|
|
98
|
+
dispatchCustomEventShim(eventName, detail) {
|
|
58
99
|
try {
|
|
59
|
-
|
|
60
|
-
this.#
|
|
61
|
-
} catch {
|
|
100
|
+
const event = new Event(eventName, { detail });
|
|
101
|
+
this.#eventTarget().dispatchEvent(event);
|
|
102
|
+
} catch (e) {
|
|
62
103
|
this.debugLog("Failed to dispatch shim event");
|
|
63
104
|
}
|
|
64
105
|
}
|
|
65
|
-
dispatchCustomEvent(
|
|
106
|
+
dispatchCustomEvent(eventName, detail) {
|
|
66
107
|
try {
|
|
67
|
-
this.#
|
|
68
|
-
} catch {
|
|
69
|
-
this.dispatchCustomEventShim(
|
|
108
|
+
this.#eventTarget().dispatchEvent(new CustomEvent(eventName, { detail }));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
this.dispatchCustomEventShim(eventName, detail);
|
|
70
111
|
}
|
|
71
112
|
}
|
|
72
|
-
emitEventToBus(
|
|
73
|
-
this.debugLog("Emitting event to client bus",
|
|
113
|
+
emitEventToBus(event) {
|
|
114
|
+
this.debugLog("Emitting event to client bus", event);
|
|
115
|
+
this.dispatchCustomEvent("tanstack-dispatch-event", event);
|
|
74
116
|
}
|
|
75
|
-
createEventPayload(
|
|
117
|
+
createEventPayload(eventSuffix, payload) {
|
|
76
118
|
return {
|
|
77
|
-
type: `${this.#
|
|
78
|
-
payload
|
|
79
|
-
pluginId: this.#
|
|
119
|
+
type: `${this.#pluginId}:${eventSuffix}`,
|
|
120
|
+
payload,
|
|
121
|
+
pluginId: this.#pluginId
|
|
80
122
|
};
|
|
81
123
|
}
|
|
82
|
-
emit(
|
|
83
|
-
if (!this.#
|
|
84
|
-
this.debugLog("Event bus client is disabled, not emitting event",
|
|
124
|
+
emit(eventSuffix, payload) {
|
|
125
|
+
if (!this.#enabled) {
|
|
126
|
+
this.debugLog("Event bus client is disabled, not emitting event", eventSuffix, payload);
|
|
85
127
|
return;
|
|
86
128
|
}
|
|
87
|
-
if (this.#
|
|
129
|
+
if (this.#internalEventTarget) {
|
|
130
|
+
this.debugLog("Emitting event to internal event target", eventSuffix, payload);
|
|
131
|
+
this.#internalEventTarget.dispatchEvent(new CustomEvent(`${this.#pluginId}:${eventSuffix}`, { detail: this.createEventPayload(eventSuffix, payload) }));
|
|
132
|
+
}
|
|
133
|
+
if (this.#failedToConnect) {
|
|
88
134
|
this.debugLog("Previously failed to connect, not emitting to bus");
|
|
89
135
|
return;
|
|
90
136
|
}
|
|
91
|
-
if (!this.#
|
|
92
|
-
this.debugLog("Bus not available, will be pushed as soon as connected")
|
|
137
|
+
if (!this.#connected) {
|
|
138
|
+
this.debugLog("Bus not available, will be pushed as soon as connected");
|
|
139
|
+
this.#queuedEvents.push(this.createEventPayload(eventSuffix, payload));
|
|
140
|
+
if (typeof CustomEvent !== "undefined" && !this.#connecting) {
|
|
141
|
+
this.#connectFunction();
|
|
142
|
+
this.startConnectLoop();
|
|
143
|
+
}
|
|
93
144
|
return;
|
|
94
145
|
}
|
|
95
|
-
return this.emitEventToBus(this.createEventPayload(
|
|
96
|
-
}
|
|
97
|
-
on(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
146
|
+
return this.emitEventToBus(this.createEventPayload(eventSuffix, payload));
|
|
147
|
+
}
|
|
148
|
+
on(eventSuffix, cb, options) {
|
|
149
|
+
const withEventTarget = options?.withEventTarget ?? false;
|
|
150
|
+
const eventName = `${this.#pluginId}:${eventSuffix}`;
|
|
151
|
+
if (withEventTarget) {
|
|
152
|
+
if (!this.#internalEventTarget) this.#internalEventTarget = new EventTarget();
|
|
153
|
+
this.#internalEventTarget.addEventListener(eventName, (e) => {
|
|
154
|
+
cb(e.detail);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (!this.#enabled) {
|
|
158
|
+
this.debugLog("Event bus client is disabled, not registering event", eventName);
|
|
159
|
+
return () => {};
|
|
160
|
+
}
|
|
161
|
+
const handler = (e) => {
|
|
162
|
+
this.debugLog("Received event from bus", e.detail);
|
|
163
|
+
cb(e.detail);
|
|
104
164
|
};
|
|
105
|
-
|
|
106
|
-
|
|
165
|
+
this.#eventTarget().addEventListener(eventName, handler);
|
|
166
|
+
this.debugLog("Registered event to bus", eventName);
|
|
167
|
+
return () => {
|
|
168
|
+
if (withEventTarget) this.#internalEventTarget?.removeEventListener(eventName, handler);
|
|
169
|
+
this.#eventTarget().removeEventListener(eventName, handler);
|
|
107
170
|
};
|
|
108
171
|
}
|
|
109
|
-
onAll(
|
|
110
|
-
if (!this.#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
onAll(cb) {
|
|
173
|
+
if (!this.#enabled) {
|
|
174
|
+
this.debugLog("Event bus client is disabled, not registering event");
|
|
175
|
+
return () => {};
|
|
176
|
+
}
|
|
177
|
+
const handler = (e) => {
|
|
178
|
+
const event = e.detail;
|
|
179
|
+
cb(event);
|
|
114
180
|
};
|
|
115
|
-
|
|
181
|
+
this.#eventTarget().addEventListener("tanstack-devtools-global", handler);
|
|
182
|
+
return () => this.#eventTarget().removeEventListener("tanstack-devtools-global", handler);
|
|
116
183
|
}
|
|
117
|
-
onAllPluginEvents(
|
|
118
|
-
if (!this.#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
184
|
+
onAllPluginEvents(cb) {
|
|
185
|
+
if (!this.#enabled) {
|
|
186
|
+
this.debugLog("Event bus client is disabled, not registering event");
|
|
187
|
+
return () => {};
|
|
188
|
+
}
|
|
189
|
+
const handler = (e) => {
|
|
190
|
+
const event = e.detail;
|
|
191
|
+
if (this.#pluginId && event.pluginId !== this.#pluginId) return;
|
|
192
|
+
cb(event);
|
|
122
193
|
};
|
|
123
|
-
|
|
194
|
+
this.#eventTarget().addEventListener("tanstack-devtools-global", handler);
|
|
195
|
+
return () => this.#eventTarget().removeEventListener("tanstack-devtools-global", handler);
|
|
124
196
|
}
|
|
125
|
-
}
|
|
197
|
+
};
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai-event-client@0.2.8_@tanstack+ai@0.14.0/node_modules/@tanstack/ai-event-client/dist/esm/index.js
|
|
200
|
+
var AiEventClient = class extends EventClient {
|
|
126
201
|
constructor() {
|
|
127
202
|
super({ pluginId: "tanstack-ai-devtools" });
|
|
128
203
|
}
|
|
129
|
-
}
|
|
204
|
+
};
|
|
205
|
+
var aiEventClient = new AiEventClient();
|
|
130
206
|
//#endregion
|
|
131
207
|
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/messages.js
|
|
132
|
-
function isContentPart(
|
|
133
|
-
return
|
|
208
|
+
function isContentPart(part) {
|
|
209
|
+
return part.type === "text" || part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document";
|
|
134
210
|
}
|
|
135
|
-
function collapseContentParts(
|
|
136
|
-
|
|
211
|
+
function collapseContentParts(parts) {
|
|
212
|
+
if (parts.length === 0) return null;
|
|
213
|
+
if (parts.every((p) => p.type === "text")) return parts.map((p) => p.content).join("") || null;
|
|
214
|
+
return parts;
|
|
137
215
|
}
|
|
138
|
-
function getTextContent(
|
|
139
|
-
|
|
216
|
+
function getTextContent(content) {
|
|
217
|
+
if (content === null) return "";
|
|
218
|
+
if (typeof content === "string") return content;
|
|
219
|
+
return content.filter((part) => part.type === "text").map((part) => part.content).join("");
|
|
140
220
|
}
|
|
141
|
-
function uiMessageToModelMessages(
|
|
142
|
-
|
|
221
|
+
function uiMessageToModelMessages(uiMessage) {
|
|
222
|
+
if (uiMessage.role === "system") return [];
|
|
223
|
+
if (uiMessage.role !== "assistant") return [buildUserOrToolMessage(uiMessage)];
|
|
224
|
+
return buildAssistantMessages(uiMessage);
|
|
143
225
|
}
|
|
144
|
-
function buildUserOrToolMessage(
|
|
145
|
-
|
|
146
|
-
for (
|
|
226
|
+
function buildUserOrToolMessage(uiMessage) {
|
|
227
|
+
const contentParts = [];
|
|
228
|
+
for (const part of uiMessage.parts) if (isContentPart(part)) contentParts.push(part);
|
|
147
229
|
return {
|
|
148
|
-
role:
|
|
149
|
-
content: collapseContentParts(
|
|
230
|
+
role: uiMessage.role,
|
|
231
|
+
content: collapseContentParts(contentParts)
|
|
150
232
|
};
|
|
151
233
|
}
|
|
152
234
|
function createSegment() {
|
|
@@ -155,101 +237,121 @@ function createSegment() {
|
|
|
155
237
|
toolCalls: []
|
|
156
238
|
};
|
|
157
239
|
}
|
|
158
|
-
function isToolCallIncluded(
|
|
159
|
-
return
|
|
240
|
+
function isToolCallIncluded(part) {
|
|
241
|
+
return part.state === "input-complete" || part.state === "approval-responded" || part.output !== void 0;
|
|
160
242
|
}
|
|
161
|
-
function buildAssistantMessages(
|
|
162
|
-
|
|
243
|
+
function buildAssistantMessages(uiMessage) {
|
|
244
|
+
const messageList = [];
|
|
245
|
+
let current = createSegment();
|
|
246
|
+
const emittedToolResultIds = /* @__PURE__ */ new Set();
|
|
163
247
|
function flushSegment() {
|
|
164
|
-
|
|
165
|
-
|
|
248
|
+
const content = collapseContentParts(current.contentParts);
|
|
249
|
+
const hasContent = content !== null;
|
|
250
|
+
const hasToolCalls = current.toolCalls.length > 0;
|
|
251
|
+
if (hasContent || hasToolCalls) messageList.push({
|
|
166
252
|
role: "assistant",
|
|
167
|
-
content
|
|
168
|
-
...
|
|
169
|
-
})
|
|
253
|
+
content,
|
|
254
|
+
...hasToolCalls && { toolCalls: current.toolCalls }
|
|
255
|
+
});
|
|
256
|
+
current = createSegment();
|
|
170
257
|
}
|
|
171
|
-
for (
|
|
258
|
+
for (const part of uiMessage.parts) switch (part.type) {
|
|
172
259
|
case "text":
|
|
173
260
|
case "image":
|
|
174
261
|
case "audio":
|
|
175
262
|
case "video":
|
|
176
263
|
case "document":
|
|
177
|
-
|
|
264
|
+
current.contentParts.push(part);
|
|
178
265
|
break;
|
|
179
266
|
case "tool-call":
|
|
180
|
-
isToolCallIncluded(
|
|
181
|
-
id:
|
|
267
|
+
if (isToolCallIncluded(part)) current.toolCalls.push({
|
|
268
|
+
id: part.id,
|
|
182
269
|
type: "function",
|
|
183
270
|
function: {
|
|
184
|
-
name:
|
|
185
|
-
arguments:
|
|
271
|
+
name: part.name,
|
|
272
|
+
arguments: part.arguments
|
|
186
273
|
}
|
|
187
274
|
});
|
|
188
275
|
break;
|
|
189
|
-
case "tool-result":
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
276
|
+
case "tool-result":
|
|
277
|
+
flushSegment();
|
|
278
|
+
if ((part.state === "complete" || part.state === "error") && !emittedToolResultIds.has(part.toolCallId)) {
|
|
279
|
+
messageList.push({
|
|
280
|
+
role: "tool",
|
|
281
|
+
content: part.content,
|
|
282
|
+
toolCallId: part.toolCallId
|
|
283
|
+
});
|
|
284
|
+
emittedToolResultIds.add(part.toolCallId);
|
|
285
|
+
}
|
|
194
286
|
}
|
|
195
287
|
flushSegment();
|
|
196
|
-
for (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
288
|
+
for (const part of uiMessage.parts) {
|
|
289
|
+
if (part.type !== "tool-call") continue;
|
|
290
|
+
if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
291
|
+
messageList.push({
|
|
292
|
+
role: "tool",
|
|
293
|
+
content: JSON.stringify(part.output),
|
|
294
|
+
toolCallId: part.id
|
|
295
|
+
});
|
|
296
|
+
emittedToolResultIds.add(part.id);
|
|
297
|
+
}
|
|
298
|
+
if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
|
|
299
|
+
const approved = part.approval.approved;
|
|
300
|
+
messageList.push({
|
|
301
|
+
role: "tool",
|
|
302
|
+
content: JSON.stringify({
|
|
303
|
+
approved,
|
|
304
|
+
...approved && { pendingExecution: true },
|
|
305
|
+
message: approved ? "User approved this action" : "User denied this action"
|
|
306
|
+
}),
|
|
307
|
+
toolCallId: part.id
|
|
308
|
+
});
|
|
309
|
+
emittedToolResultIds.add(part.id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (messageList.length === 0) messageList.push({
|
|
213
313
|
role: "assistant",
|
|
214
314
|
content: null
|
|
215
|
-
})
|
|
315
|
+
});
|
|
316
|
+
return messageList;
|
|
216
317
|
}
|
|
217
|
-
function modelMessageToUIMessage(
|
|
218
|
-
|
|
219
|
-
if (
|
|
318
|
+
function modelMessageToUIMessage(modelMessage, id) {
|
|
319
|
+
const parts = [];
|
|
320
|
+
if (modelMessage.role === "tool" && modelMessage.toolCallId) parts.push({
|
|
220
321
|
type: "tool-result",
|
|
221
|
-
toolCallId:
|
|
222
|
-
content: getTextContent(
|
|
322
|
+
toolCallId: modelMessage.toolCallId,
|
|
323
|
+
content: getTextContent(modelMessage.content),
|
|
223
324
|
state: "complete"
|
|
224
325
|
});
|
|
225
|
-
else if (Array.isArray(
|
|
326
|
+
else if (Array.isArray(modelMessage.content)) for (const part of modelMessage.content) parts.push(part);
|
|
226
327
|
else {
|
|
227
|
-
|
|
228
|
-
|
|
328
|
+
const textContent = getTextContent(modelMessage.content);
|
|
329
|
+
if (textContent) parts.push({
|
|
229
330
|
type: "text",
|
|
230
|
-
content:
|
|
331
|
+
content: textContent
|
|
231
332
|
});
|
|
232
333
|
}
|
|
233
|
-
if (
|
|
334
|
+
if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) for (const toolCall of modelMessage.toolCalls) parts.push({
|
|
234
335
|
type: "tool-call",
|
|
235
|
-
id:
|
|
236
|
-
name:
|
|
237
|
-
arguments:
|
|
336
|
+
id: toolCall.id,
|
|
337
|
+
name: toolCall.function.name,
|
|
338
|
+
arguments: toolCall.function.arguments,
|
|
238
339
|
state: "input-complete"
|
|
239
340
|
});
|
|
240
341
|
return {
|
|
241
|
-
id:
|
|
242
|
-
role:
|
|
243
|
-
parts
|
|
342
|
+
id: id || generateMessageId(),
|
|
343
|
+
role: modelMessage.role === "tool" ? "assistant" : modelMessage.role,
|
|
344
|
+
parts
|
|
244
345
|
};
|
|
245
346
|
}
|
|
246
|
-
function normalizeToUIMessage(
|
|
247
|
-
|
|
248
|
-
...
|
|
249
|
-
id:
|
|
250
|
-
createdAt:
|
|
251
|
-
}
|
|
252
|
-
|
|
347
|
+
function normalizeToUIMessage(message, generateId) {
|
|
348
|
+
if ("parts" in message) return {
|
|
349
|
+
...message,
|
|
350
|
+
id: message.id || generateId(),
|
|
351
|
+
createdAt: message.createdAt || /* @__PURE__ */ new Date()
|
|
352
|
+
};
|
|
353
|
+
else return {
|
|
354
|
+
...modelMessageToUIMessage(message, generateId()),
|
|
253
355
|
createdAt: /* @__PURE__ */ new Date()
|
|
254
356
|
};
|
|
255
357
|
}
|
|
@@ -258,958 +360,1667 @@ function generateMessageId() {
|
|
|
258
360
|
}
|
|
259
361
|
//#endregion
|
|
260
362
|
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/tool-definition.js
|
|
261
|
-
function toolDefinition(
|
|
363
|
+
function toolDefinition(config) {
|
|
262
364
|
return {
|
|
263
365
|
__toolSide: "definition",
|
|
264
|
-
...
|
|
265
|
-
server(
|
|
366
|
+
...config,
|
|
367
|
+
server(execute) {
|
|
266
368
|
return {
|
|
267
369
|
__toolSide: "server",
|
|
268
|
-
...
|
|
269
|
-
execute
|
|
370
|
+
...config,
|
|
371
|
+
execute
|
|
270
372
|
};
|
|
271
373
|
},
|
|
272
|
-
client(
|
|
374
|
+
client(execute) {
|
|
273
375
|
return {
|
|
274
376
|
__toolSide: "client",
|
|
275
|
-
...
|
|
276
|
-
execute
|
|
377
|
+
...config,
|
|
378
|
+
execute
|
|
277
379
|
};
|
|
278
380
|
}
|
|
279
381
|
};
|
|
280
382
|
}
|
|
281
383
|
//#endregion
|
|
282
384
|
//#region ../../node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/options.js
|
|
283
|
-
var
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
385
|
+
var require_options = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
386
|
+
/**
|
|
387
|
+
* Sometimes you don't allow every type to be partially parsed.
|
|
388
|
+
* For example, you may not want a partial number because it may increase its size gradually before it's complete.
|
|
389
|
+
* In this case, you can use the `Allow` object to control what types you allow to be partially parsed.
|
|
390
|
+
* @module
|
|
391
|
+
*/
|
|
392
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
393
|
+
exports.Allow = exports.ALL = exports.COLLECTION = exports.ATOM = exports.SPECIAL = exports.INF = exports._INFINITY = exports.INFINITY = exports.NAN = exports.BOOL = exports.NULL = exports.OBJ = exports.ARR = exports.NUM = exports.STR = void 0;
|
|
394
|
+
/**
|
|
395
|
+
* allow partial strings like `"hello \u12` to be parsed as `"hello "`
|
|
396
|
+
*/
|
|
397
|
+
exports.STR = 1;
|
|
398
|
+
/**
|
|
399
|
+
* allow partial numbers like `123.` to be parsed as `123`
|
|
400
|
+
*/
|
|
401
|
+
exports.NUM = 2;
|
|
402
|
+
/**
|
|
403
|
+
* allow partial arrays like `[1, 2,` to be parsed as `[1, 2]`
|
|
404
|
+
*/
|
|
405
|
+
exports.ARR = 4;
|
|
406
|
+
/**
|
|
407
|
+
* allow partial objects like `{"a": 1, "b":` to be parsed as `{"a": 1}`
|
|
408
|
+
*/
|
|
409
|
+
exports.OBJ = 8;
|
|
410
|
+
/**
|
|
411
|
+
* allow `nu` to be parsed as `null`
|
|
412
|
+
*/
|
|
413
|
+
exports.NULL = 16;
|
|
414
|
+
/**
|
|
415
|
+
* allow `tr` to be parsed as `true`, and `fa` to be parsed as `false`
|
|
416
|
+
*/
|
|
417
|
+
exports.BOOL = 32;
|
|
418
|
+
/**
|
|
419
|
+
* allow `Na` to be parsed as `NaN`
|
|
420
|
+
*/
|
|
421
|
+
exports.NAN = 64;
|
|
422
|
+
/**
|
|
423
|
+
* allow `Inf` to be parsed as `Infinity`
|
|
424
|
+
*/
|
|
425
|
+
exports.INFINITY = 128;
|
|
426
|
+
/**
|
|
427
|
+
* allow `-Inf` to be parsed as `-Infinity`
|
|
428
|
+
*/
|
|
429
|
+
exports._INFINITY = 256;
|
|
430
|
+
exports.INF = exports.INFINITY | exports._INFINITY;
|
|
431
|
+
exports.SPECIAL = exports.NULL | exports.BOOL | exports.INF | exports.NAN;
|
|
432
|
+
exports.ATOM = exports.STR | exports.NUM | exports.SPECIAL;
|
|
433
|
+
exports.COLLECTION = exports.ARR | exports.OBJ;
|
|
434
|
+
exports.ALL = exports.ATOM | exports.COLLECTION;
|
|
435
|
+
/**
|
|
436
|
+
* Control what types you allow to be partially parsed.
|
|
437
|
+
* The default is to allow all types to be partially parsed, which in most casees is the best option.
|
|
438
|
+
* @example
|
|
439
|
+
* If you don't want to allow partial objects, you can use the following code:
|
|
440
|
+
* ```ts
|
|
441
|
+
* import { Allow, parse } from "partial-json";
|
|
442
|
+
* parse(`[{"a": 1, "b": 2}, {"a": 3,`, Allow.ARR); // [ { a: 1, b: 2 } ]
|
|
443
|
+
* ```
|
|
444
|
+
* Or you can use `~` to disallow a type:
|
|
445
|
+
* ```ts
|
|
446
|
+
* parse(`[{"a": 1, "b": 2}, {"a": 3,`, ~Allow.OBJ); // [ { a: 1, b: 2 } ]
|
|
447
|
+
* ```
|
|
448
|
+
* @example
|
|
449
|
+
* If you don't want to allow partial strings, you can use the following code:
|
|
450
|
+
* ```ts
|
|
451
|
+
* import { Allow, parse } from "partial-json";
|
|
452
|
+
* parse(`["complete string", "incompl`, ~Allow.STR); // [ 'complete string' ]
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
exports.Allow = {
|
|
456
|
+
STR: exports.STR,
|
|
457
|
+
NUM: exports.NUM,
|
|
458
|
+
ARR: exports.ARR,
|
|
459
|
+
OBJ: exports.OBJ,
|
|
460
|
+
NULL: exports.NULL,
|
|
461
|
+
BOOL: exports.BOOL,
|
|
462
|
+
NAN: exports.NAN,
|
|
463
|
+
INFINITY: exports.INFINITY,
|
|
464
|
+
_INFINITY: exports._INFINITY,
|
|
465
|
+
INF: exports.INF,
|
|
466
|
+
SPECIAL: exports.SPECIAL,
|
|
467
|
+
ATOM: exports.ATOM,
|
|
468
|
+
COLLECTION: exports.COLLECTION,
|
|
469
|
+
ALL: exports.ALL
|
|
470
|
+
};
|
|
471
|
+
exports.default = exports.Allow;
|
|
472
|
+
}));
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/json-parser.js
|
|
475
|
+
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
476
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
477
|
+
if (k2 === void 0) k2 = k;
|
|
478
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
479
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) desc = {
|
|
480
|
+
enumerable: true,
|
|
306
481
|
get: function() {
|
|
307
|
-
return
|
|
482
|
+
return m[k];
|
|
308
483
|
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
484
|
+
};
|
|
485
|
+
Object.defineProperty(o, k2, desc);
|
|
486
|
+
}) : (function(o, m, k, k2) {
|
|
487
|
+
if (k2 === void 0) k2 = k;
|
|
488
|
+
o[k2] = m[k];
|
|
489
|
+
}));
|
|
490
|
+
var __exportStar = exports && exports.__exportStar || function(m, exports$1) {
|
|
491
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p);
|
|
314
492
|
};
|
|
315
|
-
Object.defineProperty(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
493
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
494
|
+
exports.Allow = exports.MalformedJSON = exports.PartialJSON = exports.parseJSON = exports.parse = void 0;
|
|
495
|
+
var options_1 = require_options();
|
|
496
|
+
Object.defineProperty(exports, "Allow", {
|
|
497
|
+
enumerable: true,
|
|
319
498
|
get: function() {
|
|
320
|
-
return
|
|
499
|
+
return options_1.Allow;
|
|
321
500
|
}
|
|
322
|
-
})
|
|
501
|
+
});
|
|
502
|
+
__exportStar(require_options(), exports);
|
|
323
503
|
var PartialJSON = class extends Error {};
|
|
324
|
-
|
|
504
|
+
exports.PartialJSON = PartialJSON;
|
|
325
505
|
var MalformedJSON = class extends Error {};
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
506
|
+
exports.MalformedJSON = MalformedJSON;
|
|
507
|
+
/**
|
|
508
|
+
* Parse incomplete JSON
|
|
509
|
+
* @param {string} jsonString Partial JSON to be parsed
|
|
510
|
+
* @param {number} allowPartial Specify what types are allowed to be partial, see {@link Allow} for details
|
|
511
|
+
* @returns The parsed JSON
|
|
512
|
+
* @throws {PartialJSON} If the JSON is incomplete (related to the `allow` parameter)
|
|
513
|
+
* @throws {MalformedJSON} If the JSON is malformed
|
|
514
|
+
*/
|
|
515
|
+
function parseJSON(jsonString, allowPartial = options_1.Allow.ALL) {
|
|
516
|
+
if (typeof jsonString !== "string") throw new TypeError(`expecting str, got ${typeof jsonString}`);
|
|
517
|
+
if (!jsonString.trim()) throw new Error(`${jsonString} is empty`);
|
|
518
|
+
return _parseJSON(jsonString.trim(), allowPartial);
|
|
519
|
+
}
|
|
520
|
+
exports.parseJSON = parseJSON;
|
|
521
|
+
var _parseJSON = (jsonString, allow) => {
|
|
522
|
+
const length = jsonString.length;
|
|
523
|
+
let index = 0;
|
|
524
|
+
const markPartialJSON = (msg) => {
|
|
525
|
+
throw new PartialJSON(`${msg} at position ${index}`);
|
|
526
|
+
};
|
|
527
|
+
const throwMalformedError = (msg) => {
|
|
528
|
+
throw new MalformedJSON(`${msg} at position ${index}`);
|
|
529
|
+
};
|
|
530
|
+
const parseAny = () => {
|
|
531
|
+
skipBlank();
|
|
532
|
+
if (index >= length) markPartialJSON("Unexpected end of input");
|
|
533
|
+
if (jsonString[index] === "\"") return parseStr();
|
|
534
|
+
if (jsonString[index] === "{") return parseObj();
|
|
535
|
+
if (jsonString[index] === "[") return parseArr();
|
|
536
|
+
if (jsonString.substring(index, index + 4) === "null" || options_1.Allow.NULL & allow && length - index < 4 && "null".startsWith(jsonString.substring(index))) {
|
|
537
|
+
index += 4;
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
if (jsonString.substring(index, index + 4) === "true" || options_1.Allow.BOOL & allow && length - index < 4 && "true".startsWith(jsonString.substring(index))) {
|
|
541
|
+
index += 4;
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
if (jsonString.substring(index, index + 5) === "false" || options_1.Allow.BOOL & allow && length - index < 5 && "false".startsWith(jsonString.substring(index))) {
|
|
545
|
+
index += 5;
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
if (jsonString.substring(index, index + 8) === "Infinity" || options_1.Allow.INFINITY & allow && length - index < 8 && "Infinity".startsWith(jsonString.substring(index))) {
|
|
549
|
+
index += 8;
|
|
550
|
+
return Infinity;
|
|
551
|
+
}
|
|
552
|
+
if (jsonString.substring(index, index + 9) === "-Infinity" || options_1.Allow._INFINITY & allow && 1 < length - index && length - index < 9 && "-Infinity".startsWith(jsonString.substring(index))) {
|
|
553
|
+
index += 9;
|
|
554
|
+
return -Infinity;
|
|
555
|
+
}
|
|
556
|
+
if (jsonString.substring(index, index + 3) === "NaN" || options_1.Allow.NAN & allow && length - index < 3 && "NaN".startsWith(jsonString.substring(index))) {
|
|
557
|
+
index += 3;
|
|
558
|
+
return NaN;
|
|
559
|
+
}
|
|
560
|
+
return parseNum();
|
|
561
|
+
};
|
|
562
|
+
const parseStr = () => {
|
|
563
|
+
const start = index;
|
|
564
|
+
let escape = false;
|
|
565
|
+
index++;
|
|
566
|
+
while (index < length && (jsonString[index] !== "\"" || escape && jsonString[index - 1] === "\\")) {
|
|
567
|
+
escape = jsonString[index] === "\\" ? !escape : false;
|
|
568
|
+
index++;
|
|
569
|
+
}
|
|
570
|
+
if (jsonString.charAt(index) == "\"") try {
|
|
571
|
+
return JSON.parse(jsonString.substring(start, ++index - Number(escape)));
|
|
343
572
|
} catch (e) {
|
|
344
573
|
throwMalformedError(String(e));
|
|
345
574
|
}
|
|
346
|
-
else if (
|
|
347
|
-
return JSON.parse(
|
|
348
|
-
} catch {
|
|
349
|
-
return JSON.parse(
|
|
575
|
+
else if (options_1.Allow.STR & allow) try {
|
|
576
|
+
return JSON.parse(jsonString.substring(start, index - Number(escape)) + "\"");
|
|
577
|
+
} catch (e) {
|
|
578
|
+
return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("\\")) + "\"");
|
|
350
579
|
}
|
|
351
580
|
markPartialJSON("Unterminated string literal");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
581
|
+
};
|
|
582
|
+
const parseObj = () => {
|
|
583
|
+
index++;
|
|
584
|
+
skipBlank();
|
|
585
|
+
const obj = {};
|
|
355
586
|
try {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
587
|
+
while (jsonString[index] !== "}") {
|
|
588
|
+
skipBlank();
|
|
589
|
+
if (index >= length && options_1.Allow.OBJ & allow) return obj;
|
|
590
|
+
const key = parseStr();
|
|
591
|
+
skipBlank();
|
|
592
|
+
index++;
|
|
360
593
|
try {
|
|
361
|
-
|
|
594
|
+
obj[key] = parseAny();
|
|
362
595
|
} catch (e) {
|
|
363
|
-
if (
|
|
364
|
-
throw e;
|
|
596
|
+
if (options_1.Allow.OBJ & allow) return obj;
|
|
597
|
+
else throw e;
|
|
365
598
|
}
|
|
366
|
-
skipBlank()
|
|
599
|
+
skipBlank();
|
|
600
|
+
if (jsonString[index] === ",") index++;
|
|
367
601
|
}
|
|
368
|
-
} catch {
|
|
369
|
-
if (
|
|
370
|
-
markPartialJSON("Expected '}' at end of object");
|
|
602
|
+
} catch (e) {
|
|
603
|
+
if (options_1.Allow.OBJ & allow) return obj;
|
|
604
|
+
else markPartialJSON("Expected '}' at end of object");
|
|
371
605
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
606
|
+
index++;
|
|
607
|
+
return obj;
|
|
608
|
+
};
|
|
609
|
+
const parseArr = () => {
|
|
610
|
+
index++;
|
|
611
|
+
const arr = [];
|
|
376
612
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
613
|
+
while (jsonString[index] !== "]") {
|
|
614
|
+
arr.push(parseAny());
|
|
615
|
+
skipBlank();
|
|
616
|
+
if (jsonString[index] === ",") index++;
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
if (options_1.Allow.ARR & allow) return arr;
|
|
380
620
|
markPartialJSON("Expected ']' at end of array");
|
|
381
621
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
622
|
+
index++;
|
|
623
|
+
return arr;
|
|
624
|
+
};
|
|
625
|
+
const parseNum = () => {
|
|
626
|
+
if (index === 0) {
|
|
627
|
+
if (jsonString === "-") throwMalformedError("Not sure what '-' is");
|
|
386
628
|
try {
|
|
387
|
-
return JSON.parse(
|
|
388
|
-
} catch (
|
|
389
|
-
if (
|
|
390
|
-
return JSON.parse(
|
|
391
|
-
} catch {}
|
|
392
|
-
throwMalformedError(String(
|
|
629
|
+
return JSON.parse(jsonString);
|
|
630
|
+
} catch (e) {
|
|
631
|
+
if (options_1.Allow.NUM & allow) try {
|
|
632
|
+
return JSON.parse(jsonString.substring(0, jsonString.lastIndexOf("e")));
|
|
633
|
+
} catch (e) {}
|
|
634
|
+
throwMalformedError(String(e));
|
|
393
635
|
}
|
|
394
636
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
637
|
+
const start = index;
|
|
638
|
+
if (jsonString[index] === "-") index++;
|
|
639
|
+
while (jsonString[index] && ",]}".indexOf(jsonString[index]) === -1) index++;
|
|
640
|
+
if (index == length && !(options_1.Allow.NUM & allow)) markPartialJSON("Unterminated number literal");
|
|
398
641
|
try {
|
|
399
|
-
return JSON.parse(
|
|
400
|
-
} catch {
|
|
401
|
-
|
|
642
|
+
return JSON.parse(jsonString.substring(start, index));
|
|
643
|
+
} catch (e) {
|
|
644
|
+
if (jsonString.substring(start, index) === "-") markPartialJSON("Not sure what '-' is");
|
|
402
645
|
try {
|
|
403
|
-
return JSON.parse(
|
|
646
|
+
return JSON.parse(jsonString.substring(start, jsonString.lastIndexOf("e")));
|
|
404
647
|
} catch (e) {
|
|
405
648
|
throwMalformedError(String(e));
|
|
406
649
|
}
|
|
407
650
|
}
|
|
408
|
-
}
|
|
409
|
-
|
|
651
|
+
};
|
|
652
|
+
const skipBlank = () => {
|
|
653
|
+
while (index < length && " \n\r ".includes(jsonString[index])) index++;
|
|
410
654
|
};
|
|
411
655
|
return parseAny();
|
|
412
656
|
};
|
|
413
|
-
|
|
414
|
-
})))()
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
657
|
+
exports.parse = parseJSON;
|
|
658
|
+
})))();
|
|
659
|
+
var PartialJSONParser = class {
|
|
660
|
+
/**
|
|
661
|
+
* Parse a potentially incomplete JSON string
|
|
662
|
+
* @param jsonString - The JSON string to parse (may be incomplete)
|
|
663
|
+
* @returns The parsed object, or undefined if parsing fails
|
|
664
|
+
*/
|
|
665
|
+
parse(jsonString) {
|
|
666
|
+
if (!jsonString || jsonString.trim() === "") return;
|
|
667
|
+
try {
|
|
668
|
+
return (0, import_dist.parse)(jsonString);
|
|
418
669
|
} catch {
|
|
419
670
|
return;
|
|
420
671
|
}
|
|
421
672
|
}
|
|
422
|
-
}
|
|
673
|
+
};
|
|
674
|
+
var defaultJSONParser = new PartialJSONParser();
|
|
423
675
|
//#endregion
|
|
424
676
|
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/message-updaters.js
|
|
425
|
-
function updateTextPart(
|
|
426
|
-
return
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
677
|
+
function updateTextPart(messages, messageId, content) {
|
|
678
|
+
return messages.map((msg) => {
|
|
679
|
+
if (msg.id !== messageId) return msg;
|
|
680
|
+
const parts = [...msg.parts];
|
|
681
|
+
const lastPart = parts.length > 0 ? parts[parts.length - 1] : null;
|
|
682
|
+
if (lastPart && lastPart.type === "text") parts[parts.length - 1] = {
|
|
430
683
|
type: "text",
|
|
431
|
-
content
|
|
432
|
-
}
|
|
684
|
+
content
|
|
685
|
+
};
|
|
686
|
+
else parts.push({
|
|
433
687
|
type: "text",
|
|
434
|
-
content
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
|
|
688
|
+
content
|
|
689
|
+
});
|
|
690
|
+
return {
|
|
691
|
+
...msg,
|
|
692
|
+
parts
|
|
438
693
|
};
|
|
439
694
|
});
|
|
440
695
|
}
|
|
441
|
-
function updateToolCallPart(
|
|
442
|
-
return
|
|
443
|
-
if (
|
|
444
|
-
|
|
696
|
+
function updateToolCallPart(messages, messageId, toolCall) {
|
|
697
|
+
return messages.map((msg) => {
|
|
698
|
+
if (msg.id !== messageId) return msg;
|
|
699
|
+
const parts = [...msg.parts];
|
|
700
|
+
const existing = parts.find((p) => p.type === "tool-call" && p.id === toolCall.id);
|
|
701
|
+
const toolCallPart = {
|
|
445
702
|
type: "tool-call",
|
|
446
|
-
id:
|
|
447
|
-
name:
|
|
448
|
-
arguments:
|
|
449
|
-
state:
|
|
450
|
-
...
|
|
451
|
-
...
|
|
703
|
+
id: toolCall.id,
|
|
704
|
+
name: toolCall.name,
|
|
705
|
+
arguments: toolCall.arguments,
|
|
706
|
+
state: toolCall.state,
|
|
707
|
+
...existing?.approval && { approval: { ...existing.approval } },
|
|
708
|
+
...existing?.output !== void 0 && { output: existing.output }
|
|
452
709
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
710
|
+
if (existing) parts[parts.indexOf(existing)] = toolCallPart;
|
|
711
|
+
else parts.push(toolCallPart);
|
|
712
|
+
return {
|
|
713
|
+
...msg,
|
|
714
|
+
parts
|
|
456
715
|
};
|
|
457
716
|
});
|
|
458
717
|
}
|
|
459
|
-
function updateToolResultPart(
|
|
460
|
-
return
|
|
461
|
-
if (
|
|
462
|
-
|
|
718
|
+
function updateToolResultPart(messages, messageId, toolCallId, content, state, error) {
|
|
719
|
+
return messages.map((msg) => {
|
|
720
|
+
if (msg.id !== messageId) return msg;
|
|
721
|
+
const parts = [...msg.parts];
|
|
722
|
+
const resultPartIndex = parts.findIndex((p) => p.type === "tool-result" && p.toolCallId === toolCallId);
|
|
723
|
+
const toolResultPart = {
|
|
463
724
|
type: "tool-result",
|
|
464
|
-
toolCallId
|
|
465
|
-
content
|
|
466
|
-
state
|
|
467
|
-
...
|
|
725
|
+
toolCallId,
|
|
726
|
+
content,
|
|
727
|
+
state,
|
|
728
|
+
...error && { error }
|
|
468
729
|
};
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
730
|
+
if (resultPartIndex >= 0) parts[resultPartIndex] = toolResultPart;
|
|
731
|
+
else parts.push(toolResultPart);
|
|
732
|
+
return {
|
|
733
|
+
...msg,
|
|
734
|
+
parts
|
|
472
735
|
};
|
|
473
736
|
});
|
|
474
737
|
}
|
|
475
|
-
function updateToolCallApproval(
|
|
476
|
-
return
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
738
|
+
function updateToolCallApproval(messages, messageId, toolCallId, approvalId) {
|
|
739
|
+
return messages.map((msg) => {
|
|
740
|
+
if (msg.id !== messageId) return msg;
|
|
741
|
+
const parts = [...msg.parts];
|
|
742
|
+
const toolCallPart = parts.find((p) => p.type === "tool-call" && p.id === toolCallId);
|
|
743
|
+
if (toolCallPart) {
|
|
744
|
+
toolCallPart.state = "approval-requested";
|
|
745
|
+
toolCallPart.approval = {
|
|
746
|
+
id: approvalId,
|
|
747
|
+
needsApproval: true
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
...msg,
|
|
752
|
+
parts
|
|
485
753
|
};
|
|
486
754
|
});
|
|
487
755
|
}
|
|
488
|
-
function updateToolCallWithOutput(
|
|
489
|
-
return
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
756
|
+
function updateToolCallWithOutput(messages, toolCallId, output, state, errorText) {
|
|
757
|
+
return messages.map((msg) => {
|
|
758
|
+
const parts = [...msg.parts];
|
|
759
|
+
const toolCallPart = parts.find((p) => p.type === "tool-call" && p.id === toolCallId);
|
|
760
|
+
if (toolCallPart) {
|
|
761
|
+
toolCallPart.output = errorText ? { error: errorText } : output;
|
|
762
|
+
if (state) toolCallPart.state = state;
|
|
763
|
+
else toolCallPart.state = "input-complete";
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
...msg,
|
|
767
|
+
parts
|
|
494
768
|
};
|
|
495
769
|
});
|
|
496
770
|
}
|
|
497
|
-
function updateToolCallApprovalResponse(
|
|
498
|
-
return
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
771
|
+
function updateToolCallApprovalResponse(messages, approvalId, approved) {
|
|
772
|
+
return messages.map((msg) => {
|
|
773
|
+
const parts = [...msg.parts];
|
|
774
|
+
const toolCallPart = parts.find((p) => p.type === "tool-call" && p.approval?.id === approvalId);
|
|
775
|
+
if (toolCallPart && toolCallPart.approval) {
|
|
776
|
+
toolCallPart.approval.approved = approved;
|
|
777
|
+
toolCallPart.state = "approval-responded";
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
...msg,
|
|
781
|
+
parts
|
|
503
782
|
};
|
|
504
783
|
});
|
|
505
784
|
}
|
|
506
|
-
function updateThinkingPart(
|
|
507
|
-
return
|
|
508
|
-
if (
|
|
509
|
-
|
|
785
|
+
function updateThinkingPart(messages, messageId, content) {
|
|
786
|
+
return messages.map((msg) => {
|
|
787
|
+
if (msg.id !== messageId) return msg;
|
|
788
|
+
const parts = [...msg.parts];
|
|
789
|
+
const thinkingPartIndex = parts.findIndex((p) => p.type === "thinking");
|
|
790
|
+
const thinkingPart = {
|
|
510
791
|
type: "thinking",
|
|
511
|
-
content
|
|
792
|
+
content
|
|
512
793
|
};
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
794
|
+
if (thinkingPartIndex >= 0) parts[thinkingPartIndex] = thinkingPart;
|
|
795
|
+
else parts.push(thinkingPart);
|
|
796
|
+
return {
|
|
797
|
+
...msg,
|
|
798
|
+
parts
|
|
516
799
|
};
|
|
517
800
|
});
|
|
518
801
|
}
|
|
519
802
|
//#endregion
|
|
520
803
|
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/strategies.js
|
|
521
804
|
var ImmediateStrategy = class {
|
|
522
|
-
shouldEmit(
|
|
523
|
-
return
|
|
524
|
-
}
|
|
525
|
-
}, h = class StreamProcessor {
|
|
526
|
-
constructor(e = {}) {
|
|
527
|
-
this.messages = [], this.messageStates = /* @__PURE__ */ new Map(), this.activeMessageIds = /* @__PURE__ */ new Set(), this.toolCallToMessage = /* @__PURE__ */ new Map(), this.pendingManualMessageId = null, this.activeRuns = /* @__PURE__ */ new Set(), this.finishReason = null, this.hasError = !1, this.isDone = !1, this.recording = null, this.recordingStartTime = 0, this.chunkStrategy = e.chunkStrategy || new ImmediateStrategy(), this.events = e.events || {}, this.jsonParser = e.jsonParser || m, this.recordingEnabled = e.recording ?? !1, e.initialMessages && (this.messages = [...e.initialMessages]);
|
|
528
|
-
}
|
|
529
|
-
setMessages(e) {
|
|
530
|
-
this.messages = [...e], this.emitMessagesChange();
|
|
805
|
+
shouldEmit(_chunk, _accumulated) {
|
|
806
|
+
return true;
|
|
531
807
|
}
|
|
532
|
-
|
|
533
|
-
|
|
808
|
+
};
|
|
809
|
+
//#endregion
|
|
810
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai@0.14.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/processor.js
|
|
811
|
+
var StreamProcessor = class StreamProcessor {
|
|
812
|
+
constructor(options = {}) {
|
|
813
|
+
this.messages = [];
|
|
814
|
+
this.messageStates = /* @__PURE__ */ new Map();
|
|
815
|
+
this.activeMessageIds = /* @__PURE__ */ new Set();
|
|
816
|
+
this.toolCallToMessage = /* @__PURE__ */ new Map();
|
|
817
|
+
this.pendingManualMessageId = null;
|
|
818
|
+
this.activeRuns = /* @__PURE__ */ new Set();
|
|
819
|
+
this.finishReason = null;
|
|
820
|
+
this.hasError = false;
|
|
821
|
+
this.isDone = false;
|
|
822
|
+
this.recording = null;
|
|
823
|
+
this.recordingStartTime = 0;
|
|
824
|
+
this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy();
|
|
825
|
+
this.events = options.events || {};
|
|
826
|
+
this.jsonParser = options.jsonParser || defaultJSONParser;
|
|
827
|
+
this.recordingEnabled = options.recording ?? false;
|
|
828
|
+
if (options.initialMessages) this.messages = [...options.initialMessages];
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Set the messages array (e.g., from persisted state)
|
|
832
|
+
*/
|
|
833
|
+
setMessages(messages) {
|
|
834
|
+
this.messages = [...messages];
|
|
835
|
+
this.emitMessagesChange();
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Add a user message to the conversation.
|
|
839
|
+
* Supports both simple string content and multimodal content arrays.
|
|
840
|
+
*
|
|
841
|
+
* @param content - The message content (string or array of content parts)
|
|
842
|
+
* @param id - Optional custom message ID (generated if not provided)
|
|
843
|
+
* @returns The created UIMessage
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```ts
|
|
847
|
+
* // Simple text message
|
|
848
|
+
* processor.addUserMessage('Hello!')
|
|
849
|
+
*
|
|
850
|
+
* // Multimodal message with image
|
|
851
|
+
* processor.addUserMessage([
|
|
852
|
+
* { type: 'text', content: 'What is in this image?' },
|
|
853
|
+
* { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }
|
|
854
|
+
* ])
|
|
855
|
+
*
|
|
856
|
+
* // With custom ID
|
|
857
|
+
* processor.addUserMessage('Hello!', 'custom-id-123')
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
addUserMessage(content, id) {
|
|
861
|
+
const parts = typeof content === "string" ? [{
|
|
534
862
|
type: "text",
|
|
535
|
-
content
|
|
536
|
-
}] :
|
|
537
|
-
|
|
863
|
+
content
|
|
864
|
+
}] : content.map((part) => {
|
|
865
|
+
return part;
|
|
866
|
+
});
|
|
867
|
+
const userMessage = {
|
|
868
|
+
id: id ?? generateMessageId(),
|
|
538
869
|
role: "user",
|
|
539
|
-
parts
|
|
870
|
+
parts,
|
|
540
871
|
createdAt: /* @__PURE__ */ new Date()
|
|
541
872
|
};
|
|
542
|
-
|
|
543
|
-
|
|
873
|
+
this.messages = [...this.messages, userMessage];
|
|
874
|
+
this.emitMessagesChange();
|
|
875
|
+
return userMessage;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Prepare for a new assistant message stream.
|
|
879
|
+
* Does NOT create the message immediately -- the message is created lazily
|
|
880
|
+
* when the first content-bearing chunk arrives via ensureAssistantMessage().
|
|
881
|
+
* This prevents empty assistant messages from flickering in the UI when
|
|
882
|
+
* auto-continuation produces no content.
|
|
883
|
+
*/
|
|
544
884
|
prepareAssistantMessage() {
|
|
545
885
|
this.resetStreamState();
|
|
546
886
|
}
|
|
547
|
-
|
|
887
|
+
/**
|
|
888
|
+
* @deprecated Use prepareAssistantMessage() instead. This eagerly creates
|
|
889
|
+
* an assistant message which can cause empty message flicker.
|
|
890
|
+
*/
|
|
891
|
+
startAssistantMessage(messageId) {
|
|
548
892
|
this.prepareAssistantMessage();
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
893
|
+
const { messageId: id } = this.ensureAssistantMessage(messageId);
|
|
894
|
+
this.pendingManualMessageId = id;
|
|
895
|
+
return id;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get the current assistant message ID (if one has been created).
|
|
899
|
+
* Returns null if prepareAssistantMessage() was called but no content
|
|
900
|
+
* has arrived yet.
|
|
901
|
+
*/
|
|
552
902
|
getCurrentAssistantMessageId() {
|
|
553
|
-
let
|
|
554
|
-
for (
|
|
555
|
-
return
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
903
|
+
let lastId = null;
|
|
904
|
+
for (const [id, state] of this.messageStates) if (state.role === "assistant") lastId = id;
|
|
905
|
+
return lastId;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Add a tool result (called by client after handling onToolCall)
|
|
909
|
+
*/
|
|
910
|
+
addToolResult(toolCallId, output, error) {
|
|
911
|
+
const messageWithToolCall = this.messages.find((msg) => msg.parts.some((p) => p.type === "tool-call" && p.id === toolCallId));
|
|
912
|
+
if (!messageWithToolCall) {
|
|
913
|
+
console.warn(`[StreamProcessor] Could not find message with tool call ${toolCallId}`);
|
|
561
914
|
return;
|
|
562
915
|
}
|
|
563
|
-
let
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
this.messages =
|
|
568
|
-
|
|
916
|
+
let updatedMessages = updateToolCallWithOutput(this.messages, toolCallId, output, error ? "input-complete" : void 0, error);
|
|
917
|
+
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
918
|
+
const toolResultState = error ? "error" : "complete";
|
|
919
|
+
updatedMessages = updateToolResultPart(updatedMessages, messageWithToolCall.id, toolCallId, content, toolResultState, error);
|
|
920
|
+
this.messages = updatedMessages;
|
|
921
|
+
this.emitMessagesChange();
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Add an approval response (called by client after handling onApprovalRequest)
|
|
925
|
+
*/
|
|
926
|
+
addToolApprovalResponse(approvalId, approved) {
|
|
927
|
+
this.messages = updateToolCallApprovalResponse(this.messages, approvalId, approved);
|
|
928
|
+
this.emitMessagesChange();
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Get the conversation as ModelMessages (for sending to LLM)
|
|
932
|
+
*/
|
|
569
933
|
toModelMessages() {
|
|
570
|
-
|
|
571
|
-
for (
|
|
572
|
-
return
|
|
934
|
+
const modelMessages = [];
|
|
935
|
+
for (const msg of this.messages) modelMessages.push(...uiMessageToModelMessages(msg));
|
|
936
|
+
return modelMessages;
|
|
573
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Get current messages
|
|
940
|
+
*/
|
|
574
941
|
getMessages() {
|
|
575
942
|
return this.messages;
|
|
576
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* Check if all tool calls in the last assistant message are complete
|
|
946
|
+
* Useful for auto-continue logic
|
|
947
|
+
*/
|
|
577
948
|
areAllToolsComplete() {
|
|
578
|
-
|
|
579
|
-
if (!
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
return
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
949
|
+
const lastAssistant = this.messages.findLast((m) => m.role === "assistant");
|
|
950
|
+
if (!lastAssistant) return true;
|
|
951
|
+
const toolParts = lastAssistant.parts.filter((p) => p.type === "tool-call");
|
|
952
|
+
if (toolParts.length === 0) return true;
|
|
953
|
+
const toolResultIds = new Set(lastAssistant.parts.filter((p) => p.type === "tool-result").map((p) => p.toolCallId));
|
|
954
|
+
return toolParts.every((part) => part.state === "approval-responded" || part.output !== void 0 && !part.approval || toolResultIds.has(part.id));
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Remove messages after a certain index (for reload/retry)
|
|
958
|
+
*/
|
|
959
|
+
removeMessagesAfter(index) {
|
|
960
|
+
this.messages = this.messages.slice(0, index + 1);
|
|
961
|
+
this.emitMessagesChange();
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Clear all messages
|
|
965
|
+
*/
|
|
588
966
|
clearMessages() {
|
|
589
|
-
this.messages = []
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
this.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
967
|
+
this.messages = [];
|
|
968
|
+
this.messageStates.clear();
|
|
969
|
+
this.activeMessageIds.clear();
|
|
970
|
+
this.toolCallToMessage.clear();
|
|
971
|
+
this.pendingManualMessageId = null;
|
|
972
|
+
this.emitMessagesChange();
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Process a stream and emit events through handlers
|
|
976
|
+
*/
|
|
977
|
+
async process(stream) {
|
|
978
|
+
this.resetStreamState();
|
|
979
|
+
if (this.recordingEnabled) this.startRecording();
|
|
980
|
+
for await (const chunk of stream) this.processChunk(chunk);
|
|
981
|
+
this.finalizeStream();
|
|
982
|
+
if (this.recording) this.recording.result = this.getResult();
|
|
983
|
+
return this.getResult();
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Process a single chunk from the stream.
|
|
987
|
+
*
|
|
988
|
+
* Central dispatch for all AG-UI events. Each event type maps to a specific
|
|
989
|
+
* handler. Events not listed in the switch are intentionally ignored
|
|
990
|
+
* (STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).
|
|
991
|
+
*
|
|
992
|
+
* @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering
|
|
993
|
+
*/
|
|
994
|
+
processChunk(chunk) {
|
|
995
|
+
if (this.recording) this.recording.chunks.push({
|
|
996
|
+
chunk,
|
|
599
997
|
timestamp: Date.now(),
|
|
600
998
|
index: this.recording.chunks.length
|
|
601
|
-
})
|
|
999
|
+
});
|
|
1000
|
+
switch (chunk.type) {
|
|
602
1001
|
case "TEXT_MESSAGE_START":
|
|
603
|
-
this.handleTextMessageStartEvent(
|
|
1002
|
+
this.handleTextMessageStartEvent(chunk);
|
|
604
1003
|
break;
|
|
605
1004
|
case "TEXT_MESSAGE_CONTENT":
|
|
606
|
-
this.handleTextMessageContentEvent(
|
|
1005
|
+
this.handleTextMessageContentEvent(chunk);
|
|
607
1006
|
break;
|
|
608
1007
|
case "TEXT_MESSAGE_END":
|
|
609
|
-
this.handleTextMessageEndEvent(
|
|
1008
|
+
this.handleTextMessageEndEvent(chunk);
|
|
610
1009
|
break;
|
|
611
1010
|
case "TOOL_CALL_START":
|
|
612
|
-
this.handleToolCallStartEvent(
|
|
1011
|
+
this.handleToolCallStartEvent(chunk);
|
|
613
1012
|
break;
|
|
614
1013
|
case "TOOL_CALL_ARGS":
|
|
615
|
-
this.handleToolCallArgsEvent(
|
|
1014
|
+
this.handleToolCallArgsEvent(chunk);
|
|
616
1015
|
break;
|
|
617
1016
|
case "TOOL_CALL_END":
|
|
618
|
-
this.handleToolCallEndEvent(
|
|
1017
|
+
this.handleToolCallEndEvent(chunk);
|
|
619
1018
|
break;
|
|
620
1019
|
case "RUN_FINISHED":
|
|
621
|
-
this.handleRunFinishedEvent(
|
|
1020
|
+
this.handleRunFinishedEvent(chunk);
|
|
622
1021
|
break;
|
|
623
1022
|
case "RUN_ERROR":
|
|
624
|
-
this.handleRunErrorEvent(
|
|
1023
|
+
this.handleRunErrorEvent(chunk);
|
|
625
1024
|
break;
|
|
626
1025
|
case "STEP_FINISHED":
|
|
627
|
-
this.handleStepFinishedEvent(
|
|
1026
|
+
this.handleStepFinishedEvent(chunk);
|
|
628
1027
|
break;
|
|
629
1028
|
case "MESSAGES_SNAPSHOT":
|
|
630
|
-
this.handleMessagesSnapshotEvent(
|
|
1029
|
+
this.handleMessagesSnapshotEvent(chunk);
|
|
631
1030
|
break;
|
|
632
1031
|
case "CUSTOM":
|
|
633
|
-
this.handleCustomEvent(
|
|
1032
|
+
this.handleCustomEvent(chunk);
|
|
634
1033
|
break;
|
|
635
1034
|
case "RUN_STARTED":
|
|
636
|
-
this.handleRunStartedEvent(
|
|
1035
|
+
this.handleRunStartedEvent(chunk);
|
|
637
1036
|
break;
|
|
638
1037
|
case "REASONING_START":
|
|
639
1038
|
case "REASONING_MESSAGE_START":
|
|
640
1039
|
case "REASONING_MESSAGE_END":
|
|
641
1040
|
case "REASONING_END": break;
|
|
642
1041
|
case "REASONING_MESSAGE_CONTENT":
|
|
643
|
-
this.handleReasoningMessageContentEvent(
|
|
1042
|
+
this.handleReasoningMessageContentEvent(chunk);
|
|
644
1043
|
break;
|
|
645
|
-
case "TOOL_CALL_RESULT": this.handleToolCallResultEvent(
|
|
1044
|
+
case "TOOL_CALL_RESULT": this.handleToolCallResultEvent(chunk);
|
|
646
1045
|
}
|
|
647
1046
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1047
|
+
/**
|
|
1048
|
+
* Create a new MessageStreamState for a message
|
|
1049
|
+
*/
|
|
1050
|
+
createMessageState(messageId, role) {
|
|
1051
|
+
const state = {
|
|
1052
|
+
id: messageId,
|
|
1053
|
+
role,
|
|
652
1054
|
totalTextContent: "",
|
|
653
1055
|
currentSegmentText: "",
|
|
654
1056
|
lastEmittedText: "",
|
|
655
1057
|
thinkingContent: "",
|
|
656
|
-
hasSeenReasoningEvents:
|
|
1058
|
+
hasSeenReasoningEvents: false,
|
|
657
1059
|
toolCalls: /* @__PURE__ */ new Map(),
|
|
658
1060
|
toolCallOrder: [],
|
|
659
|
-
hasToolCallsSinceTextStart:
|
|
660
|
-
isComplete:
|
|
1061
|
+
hasToolCallsSinceTextStart: false,
|
|
1062
|
+
isComplete: false
|
|
661
1063
|
};
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1064
|
+
this.messageStates.set(messageId, state);
|
|
1065
|
+
return state;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Get the MessageStreamState for a message
|
|
1069
|
+
*/
|
|
1070
|
+
getMessageState(messageId) {
|
|
1071
|
+
return this.messageStates.get(messageId);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Get the most recent active assistant message ID.
|
|
1075
|
+
* Used as fallback for events that don't include a messageId.
|
|
1076
|
+
*/
|
|
667
1077
|
getActiveAssistantMessageId() {
|
|
668
|
-
|
|
669
|
-
for (let
|
|
670
|
-
|
|
671
|
-
|
|
1078
|
+
const ids = Array.from(this.activeMessageIds);
|
|
1079
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
1080
|
+
const id = ids[i];
|
|
1081
|
+
const state = this.messageStates.get(id);
|
|
1082
|
+
if (state && state.role === "assistant") return id;
|
|
672
1083
|
}
|
|
673
1084
|
return null;
|
|
674
1085
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1086
|
+
/**
|
|
1087
|
+
* Ensure an active assistant message exists, creating one if needed.
|
|
1088
|
+
* Used for backward compat when events arrive without prior TEXT_MESSAGE_START.
|
|
1089
|
+
*
|
|
1090
|
+
* On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that
|
|
1091
|
+
* already exists in this.messages (e.g. from initialMessages or a prior
|
|
1092
|
+
* MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we
|
|
1093
|
+
* hydrate state from the existing message rather than creating a duplicate.
|
|
1094
|
+
*/
|
|
1095
|
+
ensureAssistantMessage(preferredId) {
|
|
1096
|
+
if (preferredId) {
|
|
1097
|
+
const state2 = this.getMessageState(preferredId);
|
|
1098
|
+
if (state2) return {
|
|
1099
|
+
messageId: preferredId,
|
|
1100
|
+
state: state2
|
|
681
1101
|
};
|
|
682
1102
|
}
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
messageId:
|
|
686
|
-
state: this.getMessageState(
|
|
1103
|
+
const activeId = this.getActiveAssistantMessageId();
|
|
1104
|
+
if (activeId) return {
|
|
1105
|
+
messageId: activeId,
|
|
1106
|
+
state: this.getMessageState(activeId)
|
|
687
1107
|
};
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
if (
|
|
691
|
-
|
|
692
|
-
this.activeMessageIds.add(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1108
|
+
if (preferredId) {
|
|
1109
|
+
const existingMsg = this.messages.find((m) => m.id === preferredId);
|
|
1110
|
+
if (existingMsg) {
|
|
1111
|
+
const state2 = this.createMessageState(preferredId, existingMsg.role);
|
|
1112
|
+
this.activeMessageIds.add(preferredId);
|
|
1113
|
+
const lastPart = existingMsg.parts.length > 0 ? existingMsg.parts[existingMsg.parts.length - 1] : null;
|
|
1114
|
+
if (lastPart && lastPart.type === "text") {
|
|
1115
|
+
state2.currentSegmentText = lastPart.content;
|
|
1116
|
+
state2.lastEmittedText = lastPart.content;
|
|
1117
|
+
state2.totalTextContent = lastPart.content;
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
messageId: preferredId,
|
|
1121
|
+
state: state2
|
|
697
1122
|
};
|
|
698
1123
|
}
|
|
699
1124
|
}
|
|
700
|
-
|
|
701
|
-
|
|
1125
|
+
const id = preferredId || generateMessageId();
|
|
1126
|
+
const assistantMessage = {
|
|
1127
|
+
id,
|
|
702
1128
|
role: "assistant",
|
|
703
1129
|
parts: [],
|
|
704
1130
|
createdAt: /* @__PURE__ */ new Date()
|
|
705
1131
|
};
|
|
706
|
-
this.messages = [...this.messages,
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1132
|
+
this.messages = [...this.messages, assistantMessage];
|
|
1133
|
+
const state = this.createMessageState(id, "assistant");
|
|
1134
|
+
this.activeMessageIds.add(id);
|
|
1135
|
+
this.pendingManualMessageId = id;
|
|
1136
|
+
this.events.onStreamStart?.();
|
|
1137
|
+
this.emitMessagesChange();
|
|
1138
|
+
return {
|
|
1139
|
+
messageId: id,
|
|
1140
|
+
state
|
|
711
1141
|
};
|
|
712
1142
|
}
|
|
713
|
-
|
|
714
|
-
|
|
1143
|
+
/**
|
|
1144
|
+
* Handle TEXT_MESSAGE_START event
|
|
1145
|
+
*/
|
|
1146
|
+
handleTextMessageStartEvent(chunk) {
|
|
1147
|
+
const { messageId, role } = chunk;
|
|
1148
|
+
const uiRole = role === "user" || role === "system" ? role : "assistant";
|
|
715
1149
|
if (this.pendingManualMessageId) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1150
|
+
const pendingId = this.pendingManualMessageId;
|
|
1151
|
+
this.pendingManualMessageId = null;
|
|
1152
|
+
if (pendingId !== messageId) {
|
|
1153
|
+
this.messages = this.messages.map((msg) => msg.id === pendingId ? {
|
|
1154
|
+
...msg,
|
|
1155
|
+
id: messageId
|
|
1156
|
+
} : msg);
|
|
1157
|
+
const existingState = this.messageStates.get(pendingId);
|
|
1158
|
+
if (existingState) {
|
|
1159
|
+
existingState.id = messageId;
|
|
1160
|
+
this.messageStates.delete(pendingId);
|
|
1161
|
+
this.messageStates.set(messageId, existingState);
|
|
1162
|
+
}
|
|
1163
|
+
this.activeMessageIds.delete(pendingId);
|
|
1164
|
+
this.activeMessageIds.add(messageId);
|
|
1165
|
+
}
|
|
1166
|
+
if (!this.messageStates.has(messageId)) {
|
|
1167
|
+
this.createMessageState(messageId, uiRole);
|
|
1168
|
+
this.activeMessageIds.add(messageId);
|
|
724
1169
|
}
|
|
725
|
-
this.
|
|
1170
|
+
this.emitMessagesChange();
|
|
726
1171
|
return;
|
|
727
1172
|
}
|
|
728
|
-
if (this.messages.find((
|
|
729
|
-
|
|
1173
|
+
if (this.messages.find((m) => m.id === messageId)) {
|
|
1174
|
+
this.activeMessageIds.add(messageId);
|
|
1175
|
+
if (!this.messageStates.has(messageId)) this.createMessageState(messageId, uiRole);
|
|
730
1176
|
else {
|
|
731
|
-
|
|
732
|
-
|
|
1177
|
+
const existingState = this.messageStates.get(messageId);
|
|
1178
|
+
if (existingState.hasToolCallsSinceTextStart) {
|
|
1179
|
+
if (existingState.currentSegmentText !== existingState.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1180
|
+
existingState.currentSegmentText = "";
|
|
1181
|
+
existingState.lastEmittedText = "";
|
|
1182
|
+
existingState.hasToolCallsSinceTextStart = false;
|
|
1183
|
+
}
|
|
733
1184
|
}
|
|
734
1185
|
return;
|
|
735
1186
|
}
|
|
736
|
-
|
|
737
|
-
id:
|
|
738
|
-
role:
|
|
1187
|
+
const newMessage = {
|
|
1188
|
+
id: messageId,
|
|
1189
|
+
role: uiRole,
|
|
739
1190
|
parts: [],
|
|
740
1191
|
createdAt: /* @__PURE__ */ new Date()
|
|
741
1192
|
};
|
|
742
|
-
this.messages = [...this.messages,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1193
|
+
this.messages = [...this.messages, newMessage];
|
|
1194
|
+
this.createMessageState(messageId, uiRole);
|
|
1195
|
+
this.activeMessageIds.add(messageId);
|
|
1196
|
+
this.events.onStreamStart?.();
|
|
1197
|
+
this.emitMessagesChange();
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Handle TEXT_MESSAGE_END event
|
|
1201
|
+
*/
|
|
1202
|
+
handleTextMessageEndEvent(chunk) {
|
|
1203
|
+
const { messageId } = chunk;
|
|
1204
|
+
const state = this.getMessageState(messageId);
|
|
1205
|
+
if (!state) return;
|
|
1206
|
+
if (state.isComplete) return;
|
|
1207
|
+
if (state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1208
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Handle MESSAGES_SNAPSHOT event
|
|
1212
|
+
*/
|
|
1213
|
+
handleMessagesSnapshotEvent(chunk) {
|
|
1214
|
+
this.resetStreamState();
|
|
1215
|
+
this.messages = [...chunk.messages];
|
|
1216
|
+
this.emitMessagesChange();
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Handle TEXT_MESSAGE_CONTENT event.
|
|
1220
|
+
*
|
|
1221
|
+
* Accumulates delta into both currentSegmentText (for UI emission) and
|
|
1222
|
+
* totalTextContent (for ProcessorResult). Lazily creates the assistant
|
|
1223
|
+
* UIMessage on first content. Uses updateTextPart() which replaces the
|
|
1224
|
+
* last TextPart or creates a new one depending on part ordering.
|
|
1225
|
+
*
|
|
1226
|
+
* @see docs/chat-architecture.md#single-shot-text-response — Text accumulation step-by-step
|
|
1227
|
+
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
1228
|
+
*/
|
|
1229
|
+
handleTextMessageContentEvent(chunk) {
|
|
1230
|
+
const { messageId, state } = this.ensureAssistantMessage(chunk.messageId);
|
|
1231
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
1232
|
+
const previousSegment = state.currentSegmentText;
|
|
1233
|
+
if (state.hasToolCallsSinceTextStart && previousSegment.length > 0 && this.isNewTextSegment(chunk, previousSegment)) {
|
|
1234
|
+
if (previousSegment !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1235
|
+
state.currentSegmentText = "";
|
|
1236
|
+
state.lastEmittedText = "";
|
|
1237
|
+
state.hasToolCallsSinceTextStart = false;
|
|
1238
|
+
}
|
|
1239
|
+
const currentText = state.currentSegmentText;
|
|
1240
|
+
let nextText = currentText;
|
|
1241
|
+
const delta = chunk.delta || "";
|
|
1242
|
+
if (delta !== "") nextText = currentText + delta;
|
|
1243
|
+
else if (chunk.content !== void 0 && chunk.content !== "") {
|
|
1244
|
+
if (chunk.content.startsWith(currentText)) nextText = chunk.content;
|
|
1245
|
+
else if (currentText.startsWith(chunk.content)) nextText = currentText;
|
|
1246
|
+
else nextText = currentText + chunk.content;
|
|
1247
|
+
}
|
|
1248
|
+
const textDelta = nextText.slice(currentText.length);
|
|
1249
|
+
state.currentSegmentText = nextText;
|
|
1250
|
+
state.totalTextContent += textDelta;
|
|
1251
|
+
const chunkPortion = chunk.delta || chunk.content || "";
|
|
1252
|
+
if (this.chunkStrategy.shouldEmit(chunkPortion, state.currentSegmentText) && state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Handle TOOL_CALL_START event.
|
|
1256
|
+
*
|
|
1257
|
+
* Creates a new InternalToolCallState entry in the toolCalls Map and appends
|
|
1258
|
+
* a ToolCallPart to the UIMessage. Duplicate toolCallId is a no-op.
|
|
1259
|
+
*
|
|
1260
|
+
* CRITICAL: This MUST be received before any TOOL_CALL_ARGS for the same
|
|
1261
|
+
* toolCallId. Args for unknown IDs are silently dropped.
|
|
1262
|
+
*
|
|
1263
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Tool call state transitions
|
|
1264
|
+
* @see docs/chat-architecture.md#parallel-tool-calls-single-shot — Parallel tracking by ID
|
|
1265
|
+
* @see docs/chat-architecture.md#adapter-contract — Ordering requirements
|
|
1266
|
+
*/
|
|
1267
|
+
handleToolCallStartEvent(chunk) {
|
|
1268
|
+
const targetMessageId = chunk.parentMessageId ?? this.getActiveAssistantMessageId();
|
|
1269
|
+
const { messageId, state } = this.ensureAssistantMessage(targetMessageId ?? void 0);
|
|
1270
|
+
state.hasToolCallsSinceTextStart = true;
|
|
1271
|
+
const toolCallId = chunk.toolCallId;
|
|
1272
|
+
if (!state.toolCalls.get(toolCallId)) {
|
|
1273
|
+
const initialState = "awaiting-input";
|
|
1274
|
+
const toolName = chunk.toolCallName;
|
|
1275
|
+
const newToolCall = {
|
|
1276
|
+
id: chunk.toolCallId,
|
|
1277
|
+
name: toolName,
|
|
771
1278
|
arguments: "",
|
|
772
|
-
state:
|
|
1279
|
+
state: initialState,
|
|
773
1280
|
parsedArguments: void 0,
|
|
774
|
-
index:
|
|
1281
|
+
index: chunk.index ?? state.toolCalls.size
|
|
775
1282
|
};
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1283
|
+
state.toolCalls.set(toolCallId, newToolCall);
|
|
1284
|
+
state.toolCallOrder.push(toolCallId);
|
|
1285
|
+
this.toolCallToMessage.set(toolCallId, messageId);
|
|
1286
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1287
|
+
id: chunk.toolCallId,
|
|
1288
|
+
name: toolName,
|
|
779
1289
|
arguments: "",
|
|
780
|
-
state:
|
|
781
|
-
})
|
|
1290
|
+
state: initialState
|
|
1291
|
+
});
|
|
1292
|
+
this.emitMessagesChange();
|
|
1293
|
+
this.events.onToolCallStateChange?.(messageId, chunk.toolCallId, initialState, "");
|
|
782
1294
|
}
|
|
783
1295
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (!
|
|
802
|
-
|
|
803
|
-
if (!
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1296
|
+
/**
|
|
1297
|
+
* Handle TOOL_CALL_ARGS event.
|
|
1298
|
+
*
|
|
1299
|
+
* Appends the delta to the tool call's accumulated arguments string.
|
|
1300
|
+
* Transitions state from awaiting-input → input-streaming on first non-empty delta.
|
|
1301
|
+
* Attempts partial JSON parse on each update for UI preview.
|
|
1302
|
+
*
|
|
1303
|
+
* If toolCallId is not found in the Map (no preceding TOOL_CALL_START),
|
|
1304
|
+
* this event is silently dropped.
|
|
1305
|
+
*
|
|
1306
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Step-by-step tool call processing
|
|
1307
|
+
*/
|
|
1308
|
+
handleToolCallArgsEvent(chunk) {
|
|
1309
|
+
const toolCallId = chunk.toolCallId;
|
|
1310
|
+
const messageId = this.toolCallToMessage.get(toolCallId);
|
|
1311
|
+
if (!messageId) return;
|
|
1312
|
+
const state = this.getMessageState(messageId);
|
|
1313
|
+
if (!state) return;
|
|
1314
|
+
const existingToolCall = state.toolCalls.get(toolCallId);
|
|
1315
|
+
if (!existingToolCall) return;
|
|
1316
|
+
const wasAwaitingInput = existingToolCall.state === "awaiting-input";
|
|
1317
|
+
existingToolCall.arguments += chunk.delta || "";
|
|
1318
|
+
if (wasAwaitingInput && chunk.delta) existingToolCall.state = "input-streaming";
|
|
1319
|
+
existingToolCall.parsedArguments = this.jsonParser.parse(existingToolCall.arguments);
|
|
1320
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1321
|
+
id: existingToolCall.id,
|
|
1322
|
+
name: existingToolCall.name,
|
|
1323
|
+
arguments: existingToolCall.arguments,
|
|
1324
|
+
state: existingToolCall.state
|
|
1325
|
+
});
|
|
1326
|
+
this.emitMessagesChange();
|
|
1327
|
+
this.events.onToolCallStateChange?.(messageId, existingToolCall.id, existingToolCall.state, existingToolCall.arguments);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.
|
|
1331
|
+
*
|
|
1332
|
+
* This event has a DUAL ROLE:
|
|
1333
|
+
* - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.
|
|
1334
|
+
* - With `result`: Signals tool was executed and result is available (from TextEngine).
|
|
1335
|
+
* Creates both output on the tool-call part AND a tool-result part.
|
|
1336
|
+
*
|
|
1337
|
+
* If `input` is provided, it overrides the accumulated string parse as the
|
|
1338
|
+
* canonical parsed arguments.
|
|
1339
|
+
*
|
|
1340
|
+
* @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation
|
|
1341
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
1342
|
+
*/
|
|
1343
|
+
handleToolCallEndEvent(chunk) {
|
|
1344
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
1345
|
+
if (!messageId) return;
|
|
1346
|
+
const msgState = this.getMessageState(messageId);
|
|
1347
|
+
if (!msgState) return;
|
|
1348
|
+
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
|
|
1349
|
+
if (existingToolCall && existingToolCall.state !== "input-complete") {
|
|
1350
|
+
if (chunk.input !== void 0 && !existingToolCall.arguments) existingToolCall.arguments = JSON.stringify(chunk.input);
|
|
1351
|
+
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
|
|
1352
|
+
this.completeToolCall(messageId, index, existingToolCall);
|
|
1353
|
+
if (chunk.input !== void 0) existingToolCall.parsedArguments = chunk.input;
|
|
809
1354
|
}
|
|
810
|
-
if (
|
|
811
|
-
let
|
|
1355
|
+
if (chunk.result) {
|
|
1356
|
+
let output;
|
|
812
1357
|
try {
|
|
813
|
-
|
|
1358
|
+
output = JSON.parse(chunk.result);
|
|
814
1359
|
} catch {
|
|
815
|
-
|
|
1360
|
+
output = chunk.result;
|
|
816
1361
|
}
|
|
817
|
-
this.messages = updateToolCallWithOutput(this.messages,
|
|
1362
|
+
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output);
|
|
1363
|
+
const resultState = "complete";
|
|
1364
|
+
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.result, resultState);
|
|
1365
|
+
this.emitMessagesChange();
|
|
818
1366
|
}
|
|
819
1367
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1368
|
+
/**
|
|
1369
|
+
* Handle TOOL_CALL_RESULT event (AG-UI spec).
|
|
1370
|
+
*
|
|
1371
|
+
* Creates a tool-result part and updates the tool-call output field,
|
|
1372
|
+
* mirroring the logic from TOOL_CALL_END when it carries a result.
|
|
1373
|
+
* This is the spec-compliant path for delivering tool results to the client.
|
|
1374
|
+
*/
|
|
1375
|
+
handleToolCallResultEvent(chunk) {
|
|
1376
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
1377
|
+
if (!messageId) return;
|
|
1378
|
+
let output;
|
|
824
1379
|
try {
|
|
825
|
-
|
|
1380
|
+
output = JSON.parse(chunk.content);
|
|
826
1381
|
} catch {
|
|
827
|
-
|
|
1382
|
+
output = chunk.content;
|
|
1383
|
+
}
|
|
1384
|
+
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output);
|
|
1385
|
+
const resultState = "complete";
|
|
1386
|
+
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.content, resultState);
|
|
1387
|
+
this.emitMessagesChange();
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Handle RUN_STARTED event.
|
|
1391
|
+
*
|
|
1392
|
+
* Registers the run so that RUN_FINISHED can determine whether other
|
|
1393
|
+
* runs are still active before finalizing.
|
|
1394
|
+
*/
|
|
1395
|
+
handleRunStartedEvent(chunk) {
|
|
1396
|
+
this.activeRuns.add(chunk.runId);
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Handle RUN_FINISHED event.
|
|
1400
|
+
*
|
|
1401
|
+
* Records the finishReason and removes the run from activeRuns.
|
|
1402
|
+
* Only finalizes when no more runs are active, so that concurrent
|
|
1403
|
+
* runs don't interfere with each other.
|
|
1404
|
+
*
|
|
1405
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics
|
|
1406
|
+
* @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory
|
|
1407
|
+
*/
|
|
1408
|
+
handleRunFinishedEvent(chunk) {
|
|
1409
|
+
this.finishReason = chunk.finishReason ?? null;
|
|
1410
|
+
this.activeRuns.delete(chunk.runId);
|
|
1411
|
+
if (this.activeRuns.size === 0) {
|
|
1412
|
+
this.isDone = true;
|
|
1413
|
+
this.completeAllToolCalls();
|
|
1414
|
+
this.finalizeStream();
|
|
828
1415
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
this.
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Handle RUN_ERROR event
|
|
1419
|
+
*/
|
|
1420
|
+
handleRunErrorEvent(chunk) {
|
|
1421
|
+
this.hasError = true;
|
|
1422
|
+
const runId = chunk.runId;
|
|
1423
|
+
if (runId) this.activeRuns.delete(runId);
|
|
1424
|
+
else this.activeRuns.clear();
|
|
1425
|
+
this.ensureAssistantMessage();
|
|
1426
|
+
const errorMessage = chunk.message || chunk.error?.message || "An error occurred";
|
|
1427
|
+
this.events.onError?.(new Error(errorMessage));
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Handle STEP_FINISHED event (for thinking/reasoning content).
|
|
1431
|
+
*
|
|
1432
|
+
* Accumulates delta into thinkingContent and updates a single ThinkingPart
|
|
1433
|
+
* in the UIMessage (replaced in-place, not appended).
|
|
1434
|
+
*
|
|
1435
|
+
* @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow
|
|
1436
|
+
*/
|
|
1437
|
+
handleStepFinishedEvent(chunk) {
|
|
1438
|
+
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
1439
|
+
if (state.hasSeenReasoningEvents) return;
|
|
1440
|
+
const previous = state.thinkingContent;
|
|
1441
|
+
let nextThinking = previous;
|
|
1442
|
+
if (chunk.delta && chunk.delta !== "") nextThinking = previous + chunk.delta;
|
|
1443
|
+
else if (chunk.content && chunk.content !== "") {
|
|
1444
|
+
if (chunk.content.startsWith(previous)) nextThinking = chunk.content;
|
|
1445
|
+
else if (previous.startsWith(chunk.content)) nextThinking = previous;
|
|
1446
|
+
else nextThinking = previous + chunk.content;
|
|
1447
|
+
}
|
|
1448
|
+
state.thinkingContent = nextThinking;
|
|
1449
|
+
this.messages = updateThinkingPart(this.messages, messageId, state.thinkingContent);
|
|
1450
|
+
this.emitMessagesChange();
|
|
1451
|
+
this.events.onThinkingUpdate?.(messageId, state.thinkingContent);
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).
|
|
1455
|
+
*
|
|
1456
|
+
* Accumulates reasoning delta into thinkingContent and updates the ThinkingPart
|
|
1457
|
+
* in the UIMessage.
|
|
1458
|
+
*/
|
|
1459
|
+
handleReasoningMessageContentEvent(chunk) {
|
|
1460
|
+
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
1461
|
+
state.hasSeenReasoningEvents = true;
|
|
1462
|
+
const delta = chunk.delta || "";
|
|
1463
|
+
state.thinkingContent = state.thinkingContent + delta;
|
|
1464
|
+
this.messages = updateThinkingPart(this.messages, messageId, state.thinkingContent);
|
|
1465
|
+
this.emitMessagesChange();
|
|
1466
|
+
this.events.onThinkingUpdate?.(messageId, state.thinkingContent);
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Handle CUSTOM event.
|
|
1470
|
+
*
|
|
1471
|
+
* Handles special custom events emitted by the TextEngine (not adapters):
|
|
1472
|
+
* - 'tool-input-available': Client tool needs execution. Fires onToolCall.
|
|
1473
|
+
* - 'approval-requested': Tool needs user approval. Updates tool-call part
|
|
1474
|
+
* state and fires onApprovalRequest.
|
|
1475
|
+
*
|
|
1476
|
+
* @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details
|
|
1477
|
+
*/
|
|
1478
|
+
handleCustomEvent(chunk) {
|
|
1479
|
+
const messageId = this.getActiveAssistantMessageId();
|
|
1480
|
+
if (chunk.name === "tool-input-available" && chunk.value) {
|
|
1481
|
+
const { toolCallId, toolName, input } = chunk.value;
|
|
860
1482
|
this.events.onToolCall?.({
|
|
861
|
-
toolCallId
|
|
862
|
-
toolName
|
|
863
|
-
input
|
|
1483
|
+
toolCallId,
|
|
1484
|
+
toolName,
|
|
1485
|
+
input
|
|
864
1486
|
});
|
|
865
1487
|
return;
|
|
866
1488
|
}
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1489
|
+
if (chunk.name === "approval-requested" && chunk.value) {
|
|
1490
|
+
const { toolCallId, toolName, input, approval } = chunk.value;
|
|
1491
|
+
const resolvedMessageId = messageId ?? this.toolCallToMessage.get(toolCallId);
|
|
1492
|
+
if (resolvedMessageId) {
|
|
1493
|
+
this.messages = updateToolCallApproval(this.messages, resolvedMessageId, toolCallId, approval.id);
|
|
1494
|
+
this.emitMessagesChange();
|
|
1495
|
+
}
|
|
1496
|
+
this.events.onApprovalRequest?.({
|
|
1497
|
+
toolCallId,
|
|
1498
|
+
toolName,
|
|
1499
|
+
input,
|
|
1500
|
+
approvalId: approval.id
|
|
874
1501
|
});
|
|
875
1502
|
return;
|
|
876
1503
|
}
|
|
877
1504
|
if (this.events.onCustomEvent) {
|
|
878
|
-
|
|
879
|
-
this.events.onCustomEvent(
|
|
1505
|
+
const toolCallId = chunk.value && typeof chunk.value === "object" ? chunk.value.toolCallId : void 0;
|
|
1506
|
+
this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId });
|
|
880
1507
|
}
|
|
881
1508
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1509
|
+
/**
|
|
1510
|
+
* Detect if an incoming content chunk represents a NEW text segment
|
|
1511
|
+
*/
|
|
1512
|
+
isNewTextSegment(chunk, previous) {
|
|
1513
|
+
if (chunk.content !== void 0) {
|
|
1514
|
+
if (chunk.content.length < previous.length) return true;
|
|
1515
|
+
if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) return true;
|
|
1516
|
+
}
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Complete all tool calls across all active messages — safety net for stream termination.
|
|
1521
|
+
*
|
|
1522
|
+
* Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call
|
|
1523
|
+
* not yet in input-complete state. Handles cases where TOOL_CALL_END was
|
|
1524
|
+
* missed (adapter bug, network error, aborted stream).
|
|
1525
|
+
*
|
|
1526
|
+
* @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior
|
|
1527
|
+
*/
|
|
885
1528
|
completeAllToolCalls() {
|
|
886
|
-
for (
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1529
|
+
for (const messageId of this.activeMessageIds) this.completeAllToolCallsForMessage(messageId);
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Complete all tool calls for a specific message
|
|
1533
|
+
*/
|
|
1534
|
+
completeAllToolCallsForMessage(messageId) {
|
|
1535
|
+
const state = this.getMessageState(messageId);
|
|
1536
|
+
if (!state) return;
|
|
1537
|
+
state.toolCalls.forEach((toolCall, id) => {
|
|
1538
|
+
if (toolCall.state !== "input-complete") {
|
|
1539
|
+
const index = state.toolCallOrder.indexOf(id);
|
|
1540
|
+
this.completeToolCall(messageId, index, toolCall);
|
|
894
1541
|
}
|
|
895
1542
|
});
|
|
896
1543
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1544
|
+
/**
|
|
1545
|
+
* Mark a tool call as complete and emit event
|
|
1546
|
+
*/
|
|
1547
|
+
completeToolCall(messageId, _index, toolCall) {
|
|
1548
|
+
toolCall.state = "input-complete";
|
|
1549
|
+
toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments);
|
|
1550
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1551
|
+
id: toolCall.id,
|
|
1552
|
+
name: toolCall.name,
|
|
1553
|
+
arguments: toolCall.arguments,
|
|
902
1554
|
state: "input-complete"
|
|
903
|
-
})
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1555
|
+
});
|
|
1556
|
+
this.emitMessagesChange();
|
|
1557
|
+
this.events.onToolCallStateChange?.(messageId, toolCall.id, "input-complete", toolCall.arguments);
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Emit pending text update for a specific message.
|
|
1561
|
+
*
|
|
1562
|
+
* Calls updateTextPart() which has critical append-vs-replace logic:
|
|
1563
|
+
* - If last UIMessage part is TextPart → replaces its content (same segment).
|
|
1564
|
+
* - If last part is anything else → pushes new TextPart (new segment after tools).
|
|
1565
|
+
*
|
|
1566
|
+
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
1567
|
+
*/
|
|
1568
|
+
emitTextUpdateForMessage(messageId) {
|
|
1569
|
+
const state = this.getMessageState(messageId);
|
|
1570
|
+
if (!state) return;
|
|
1571
|
+
state.lastEmittedText = state.currentSegmentText;
|
|
1572
|
+
this.messages = updateTextPart(this.messages, messageId, state.currentSegmentText);
|
|
1573
|
+
this.emitMessagesChange();
|
|
1574
|
+
this.events.onTextUpdate?.(messageId, state.currentSegmentText);
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Emit messages change event
|
|
1578
|
+
*/
|
|
909
1579
|
emitMessagesChange() {
|
|
910
1580
|
this.events.onMessagesChange?.([...this.messages]);
|
|
911
1581
|
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Finalize the stream — complete all pending operations.
|
|
1584
|
+
*
|
|
1585
|
+
* Called when the async iterable ends (stream closed). Acts as the final
|
|
1586
|
+
* safety net: completes any remaining tool calls, flushes un-emitted text,
|
|
1587
|
+
* and fires onStreamEnd.
|
|
1588
|
+
*
|
|
1589
|
+
* @see docs/chat-architecture.md#single-shot-text-response — Finalization step
|
|
1590
|
+
*/
|
|
912
1591
|
finalizeStream() {
|
|
913
|
-
let
|
|
914
|
-
for (
|
|
915
|
-
|
|
916
|
-
if (!
|
|
917
|
-
this.completeAllToolCallsForMessage(
|
|
918
|
-
|
|
919
|
-
|
|
1592
|
+
let lastAssistantMessage;
|
|
1593
|
+
for (const messageId of this.activeMessageIds) {
|
|
1594
|
+
const state = this.getMessageState(messageId);
|
|
1595
|
+
if (!state) continue;
|
|
1596
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
1597
|
+
if (state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
1598
|
+
state.isComplete = true;
|
|
1599
|
+
const msg = this.messages.find((m) => m.id === messageId);
|
|
1600
|
+
if (msg && msg.role === "assistant") lastAssistantMessage = msg;
|
|
920
1601
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1602
|
+
this.activeMessageIds.clear();
|
|
1603
|
+
if (lastAssistantMessage && !this.hasError) {
|
|
1604
|
+
if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {
|
|
1605
|
+
this.messages = this.messages.filter((m) => m.id !== lastAssistantMessage.id);
|
|
1606
|
+
this.emitMessagesChange();
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
924
1609
|
}
|
|
925
|
-
|
|
1610
|
+
if (lastAssistantMessage) this.events.onStreamEnd?.(lastAssistantMessage);
|
|
926
1611
|
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Get completed tool calls in API format (aggregated across all messages)
|
|
1614
|
+
*/
|
|
927
1615
|
getCompletedToolCalls() {
|
|
928
|
-
|
|
929
|
-
for (
|
|
930
|
-
id:
|
|
1616
|
+
const result = [];
|
|
1617
|
+
for (const state of this.messageStates.values()) for (const tc of state.toolCalls.values()) if (tc.state === "input-complete") result.push({
|
|
1618
|
+
id: tc.id,
|
|
931
1619
|
type: "function",
|
|
932
1620
|
function: {
|
|
933
|
-
name:
|
|
934
|
-
arguments:
|
|
1621
|
+
name: tc.name,
|
|
1622
|
+
arguments: tc.arguments
|
|
935
1623
|
}
|
|
936
1624
|
});
|
|
937
|
-
return
|
|
1625
|
+
return result;
|
|
938
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get current result (aggregated across all messages)
|
|
1629
|
+
*/
|
|
939
1630
|
getResult() {
|
|
940
|
-
|
|
941
|
-
|
|
1631
|
+
const toolCalls = this.getCompletedToolCalls();
|
|
1632
|
+
let content = "";
|
|
1633
|
+
let thinking = "";
|
|
1634
|
+
for (const state of this.messageStates.values()) {
|
|
1635
|
+
content += state.totalTextContent;
|
|
1636
|
+
thinking += state.thinkingContent;
|
|
1637
|
+
}
|
|
942
1638
|
return {
|
|
943
|
-
content
|
|
944
|
-
thinking:
|
|
945
|
-
toolCalls:
|
|
1639
|
+
content,
|
|
1640
|
+
thinking: thinking || void 0,
|
|
1641
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
946
1642
|
finishReason: this.finishReason
|
|
947
1643
|
};
|
|
948
1644
|
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Get current processor state (aggregated across all messages)
|
|
1647
|
+
*/
|
|
949
1648
|
getState() {
|
|
950
|
-
let
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1649
|
+
let content = "";
|
|
1650
|
+
let thinking = "";
|
|
1651
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1652
|
+
const toolCallOrder = [];
|
|
1653
|
+
for (const state of this.messageStates.values()) {
|
|
1654
|
+
content += state.totalTextContent;
|
|
1655
|
+
thinking += state.thinkingContent;
|
|
1656
|
+
for (const [id, tc] of state.toolCalls) toolCalls.set(id, tc);
|
|
1657
|
+
toolCallOrder.push(...state.toolCallOrder);
|
|
955
1658
|
}
|
|
956
1659
|
return {
|
|
957
|
-
content
|
|
958
|
-
thinking
|
|
959
|
-
toolCalls
|
|
960
|
-
toolCallOrder
|
|
1660
|
+
content,
|
|
1661
|
+
thinking,
|
|
1662
|
+
toolCalls,
|
|
1663
|
+
toolCallOrder,
|
|
961
1664
|
finishReason: this.finishReason,
|
|
962
1665
|
done: this.isDone
|
|
963
1666
|
};
|
|
964
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Start recording chunks
|
|
1670
|
+
*/
|
|
965
1671
|
startRecording() {
|
|
966
|
-
this.recordingEnabled =
|
|
1672
|
+
this.recordingEnabled = true;
|
|
1673
|
+
this.recordingStartTime = Date.now();
|
|
1674
|
+
this.recording = {
|
|
967
1675
|
version: "1.0",
|
|
968
1676
|
timestamp: this.recordingStartTime,
|
|
969
1677
|
chunks: []
|
|
970
1678
|
};
|
|
971
1679
|
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Get the current recording
|
|
1682
|
+
*/
|
|
972
1683
|
getRecording() {
|
|
973
1684
|
return this.recording;
|
|
974
1685
|
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Reset stream state (but keep messages)
|
|
1688
|
+
*/
|
|
975
1689
|
resetStreamState() {
|
|
976
|
-
this.messageStates.clear()
|
|
977
|
-
|
|
1690
|
+
this.messageStates.clear();
|
|
1691
|
+
this.activeMessageIds.clear();
|
|
1692
|
+
this.activeRuns.clear();
|
|
1693
|
+
this.toolCallToMessage.clear();
|
|
1694
|
+
this.pendingManualMessageId = null;
|
|
1695
|
+
this.finishReason = null;
|
|
1696
|
+
this.hasError = false;
|
|
1697
|
+
this.isDone = false;
|
|
1698
|
+
this.chunkStrategy.reset?.();
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Full reset (including messages)
|
|
1702
|
+
*/
|
|
978
1703
|
reset() {
|
|
979
|
-
this.resetStreamState()
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1704
|
+
this.resetStreamState();
|
|
1705
|
+
this.messages = [];
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Check if a message contains only whitespace text and no other meaningful parts
|
|
1709
|
+
* (no tool calls, tool results, thinking, etc.)
|
|
1710
|
+
*/
|
|
1711
|
+
isWhitespaceOnlyMessage(message) {
|
|
1712
|
+
if (message.parts.length === 0) return false;
|
|
1713
|
+
return message.parts.every((part) => part.type === "text" && part.content.trim() === "");
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Replay a recording through the processor
|
|
1717
|
+
*/
|
|
1718
|
+
static async replay(recording, options) {
|
|
1719
|
+
return new StreamProcessor(options).process(createReplayStream(recording));
|
|
986
1720
|
}
|
|
987
1721
|
};
|
|
988
|
-
function createReplayStream(
|
|
1722
|
+
function createReplayStream(recording) {
|
|
989
1723
|
return { async *[Symbol.asyncIterator]() {
|
|
990
|
-
for (
|
|
1724
|
+
for (const { chunk } of recording.chunks) yield chunk;
|
|
991
1725
|
} };
|
|
992
1726
|
}
|
|
993
1727
|
//#endregion
|
|
994
1728
|
//#region ../../node_modules/.pnpm/@tanstack+ai-client@0.8.0/node_modules/@tanstack/ai-client/dist/esm/events.js
|
|
995
1729
|
var ChatClientEventEmitter = class {
|
|
996
|
-
constructor(
|
|
997
|
-
this.clientId =
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
this.emitEvent("client:
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1730
|
+
constructor(clientId) {
|
|
1731
|
+
this.clientId = clientId;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Emit client created event
|
|
1735
|
+
*/
|
|
1736
|
+
clientCreated(initialMessageCount) {
|
|
1737
|
+
this.emitEvent("client:created", { initialMessageCount });
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Emit loading state changed event
|
|
1741
|
+
*/
|
|
1742
|
+
loadingChanged(isLoading) {
|
|
1743
|
+
this.emitEvent("client:loading:changed", { isLoading });
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Emit error state changed event
|
|
1747
|
+
*/
|
|
1748
|
+
errorChanged(error) {
|
|
1749
|
+
this.emitEvent("client:error:changed", { error });
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Emit text update events (combines processor and client events)
|
|
1753
|
+
*/
|
|
1754
|
+
textUpdated(streamId, messageId, content) {
|
|
1009
1755
|
this.emitEvent("text:chunk:content", {
|
|
1010
|
-
streamId
|
|
1011
|
-
messageId
|
|
1012
|
-
content
|
|
1756
|
+
streamId,
|
|
1757
|
+
messageId,
|
|
1758
|
+
content
|
|
1013
1759
|
});
|
|
1014
1760
|
}
|
|
1015
|
-
|
|
1761
|
+
/**
|
|
1762
|
+
* Emit tool call state change events (combines processor and client events)
|
|
1763
|
+
*/
|
|
1764
|
+
toolCallStateChanged(streamId, messageId, toolCallId, toolName, state, args) {
|
|
1016
1765
|
this.emitEvent("tools:call:updated", {
|
|
1017
|
-
streamId
|
|
1018
|
-
messageId
|
|
1019
|
-
toolCallId
|
|
1020
|
-
toolName
|
|
1021
|
-
state
|
|
1022
|
-
arguments:
|
|
1766
|
+
streamId,
|
|
1767
|
+
messageId,
|
|
1768
|
+
toolCallId,
|
|
1769
|
+
toolName,
|
|
1770
|
+
state,
|
|
1771
|
+
arguments: args
|
|
1023
1772
|
});
|
|
1024
1773
|
}
|
|
1025
|
-
|
|
1774
|
+
/**
|
|
1775
|
+
* Emit tool result state change event
|
|
1776
|
+
*/
|
|
1777
|
+
/**
|
|
1778
|
+
* Emit thinking update event
|
|
1779
|
+
*/
|
|
1780
|
+
thinkingUpdated(streamId, messageId, content, delta) {
|
|
1026
1781
|
this.emitEvent("text:chunk:thinking", {
|
|
1027
|
-
streamId
|
|
1028
|
-
messageId
|
|
1029
|
-
content
|
|
1030
|
-
delta
|
|
1782
|
+
streamId,
|
|
1783
|
+
messageId,
|
|
1784
|
+
content,
|
|
1785
|
+
delta
|
|
1031
1786
|
});
|
|
1032
1787
|
}
|
|
1033
|
-
|
|
1788
|
+
/**
|
|
1789
|
+
* Emit approval requested event
|
|
1790
|
+
*/
|
|
1791
|
+
approvalRequested(streamId, messageId, toolCallId, toolName, input, approvalId) {
|
|
1034
1792
|
this.emitEvent("tools:approval:requested", {
|
|
1035
|
-
streamId
|
|
1036
|
-
messageId
|
|
1037
|
-
toolCallId
|
|
1038
|
-
toolName
|
|
1039
|
-
input
|
|
1040
|
-
approvalId
|
|
1793
|
+
streamId,
|
|
1794
|
+
messageId,
|
|
1795
|
+
toolCallId,
|
|
1796
|
+
toolName,
|
|
1797
|
+
input,
|
|
1798
|
+
approvalId
|
|
1041
1799
|
});
|
|
1042
1800
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1801
|
+
/**
|
|
1802
|
+
* Emit message appended event
|
|
1803
|
+
*/
|
|
1804
|
+
messageAppended(uiMessage, streamId) {
|
|
1805
|
+
const content = uiMessage.parts.filter((part) => part.type === "text").map((part) => part.content).join(" ");
|
|
1045
1806
|
this.emitEvent("text:message:created", {
|
|
1046
|
-
streamId
|
|
1047
|
-
messageId:
|
|
1048
|
-
role:
|
|
1049
|
-
content
|
|
1050
|
-
parts:
|
|
1807
|
+
streamId,
|
|
1808
|
+
messageId: uiMessage.id,
|
|
1809
|
+
role: uiMessage.role,
|
|
1810
|
+
content,
|
|
1811
|
+
parts: uiMessage.parts
|
|
1051
1812
|
});
|
|
1052
1813
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1814
|
+
/**
|
|
1815
|
+
* Emit message sent event.
|
|
1816
|
+
* Supports both simple string content and multimodal content arrays.
|
|
1817
|
+
*
|
|
1818
|
+
* @param messageId - The ID of the sent message
|
|
1819
|
+
* @param content - The message content (string or array of ContentPart for multimodal)
|
|
1820
|
+
*/
|
|
1821
|
+
messageSent(messageId, content) {
|
|
1822
|
+
const textContent = typeof content === "string" ? content : content.filter((part) => part.type === "text").map((part) => part.content).join("");
|
|
1055
1823
|
this.emitEvent("text:message:created", {
|
|
1056
|
-
messageId
|
|
1824
|
+
messageId,
|
|
1057
1825
|
role: "user",
|
|
1058
|
-
content:
|
|
1059
|
-
...Array.isArray(
|
|
1060
|
-
})
|
|
1061
|
-
|
|
1826
|
+
content: textContent,
|
|
1827
|
+
...Array.isArray(content) && { parts: content }
|
|
1828
|
+
});
|
|
1829
|
+
this.emitEvent("text:message:user", {
|
|
1830
|
+
messageId,
|
|
1062
1831
|
role: "user",
|
|
1063
|
-
content:
|
|
1064
|
-
...Array.isArray(
|
|
1832
|
+
content: textContent,
|
|
1833
|
+
...Array.isArray(content) && { parts: content }
|
|
1065
1834
|
});
|
|
1066
1835
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1836
|
+
/**
|
|
1837
|
+
* Emit reloaded event
|
|
1838
|
+
*/
|
|
1839
|
+
reloaded(fromMessageIndex) {
|
|
1840
|
+
this.emitEvent("client:reloaded", { fromMessageIndex });
|
|
1069
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Emit stopped event
|
|
1844
|
+
*/
|
|
1070
1845
|
stopped() {
|
|
1071
1846
|
this.emitEvent("client:stopped");
|
|
1072
1847
|
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Emit messages cleared event
|
|
1850
|
+
*/
|
|
1073
1851
|
messagesCleared() {
|
|
1074
1852
|
this.emitEvent("client:messages:cleared");
|
|
1075
1853
|
}
|
|
1076
|
-
|
|
1854
|
+
/**
|
|
1855
|
+
* Emit tool result added event
|
|
1856
|
+
*/
|
|
1857
|
+
toolResultAdded(toolCallId, toolName, output, state) {
|
|
1077
1858
|
this.emitEvent("tools:result:added", {
|
|
1078
|
-
toolCallId
|
|
1079
|
-
toolName
|
|
1080
|
-
output
|
|
1081
|
-
state
|
|
1859
|
+
toolCallId,
|
|
1860
|
+
toolName,
|
|
1861
|
+
output,
|
|
1862
|
+
state
|
|
1082
1863
|
});
|
|
1083
1864
|
}
|
|
1084
|
-
|
|
1865
|
+
/**
|
|
1866
|
+
* Emit tool approval responded event
|
|
1867
|
+
*/
|
|
1868
|
+
toolApprovalResponded(approvalId, toolCallId, approved) {
|
|
1085
1869
|
this.emitEvent("tools:approval:responded", {
|
|
1086
|
-
approvalId
|
|
1087
|
-
toolCallId
|
|
1088
|
-
approved
|
|
1870
|
+
approvalId,
|
|
1871
|
+
toolCallId,
|
|
1872
|
+
approved
|
|
1089
1873
|
});
|
|
1090
1874
|
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1875
|
+
};
|
|
1876
|
+
var DefaultChatClientEventEmitter = class extends ChatClientEventEmitter {
|
|
1877
|
+
/**
|
|
1878
|
+
* Emit an event with automatic clientId and timestamp for client/tool events
|
|
1879
|
+
*/
|
|
1880
|
+
emitEvent(eventName, data) {
|
|
1881
|
+
if (eventName.startsWith("client:") || eventName.startsWith("tools:") || eventName.startsWith("text:")) aiEventClient.emit(eventName, {
|
|
1882
|
+
...data,
|
|
1095
1883
|
clientId: this.clientId,
|
|
1096
1884
|
source: "client",
|
|
1097
1885
|
timestamp: Date.now()
|
|
1098
|
-
})
|
|
1099
|
-
|
|
1886
|
+
});
|
|
1887
|
+
else aiEventClient.emit(eventName, {
|
|
1888
|
+
...data,
|
|
1100
1889
|
timestamp: Date.now()
|
|
1101
1890
|
});
|
|
1102
1891
|
}
|
|
1103
1892
|
};
|
|
1104
1893
|
//#endregion
|
|
1105
1894
|
//#region ../../node_modules/.pnpm/@tanstack+ai-client@0.8.0/node_modules/@tanstack/ai-client/dist/esm/connection-adapters.js
|
|
1106
|
-
function mergeHeaders(
|
|
1107
|
-
if (!
|
|
1108
|
-
if (
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
})
|
|
1113
|
-
|
|
1114
|
-
|
|
1895
|
+
function mergeHeaders(customHeaders) {
|
|
1896
|
+
if (!customHeaders) return {};
|
|
1897
|
+
if (customHeaders instanceof Headers) {
|
|
1898
|
+
const result = {};
|
|
1899
|
+
customHeaders.forEach((value, key) => {
|
|
1900
|
+
result[key] = value;
|
|
1901
|
+
});
|
|
1902
|
+
return result;
|
|
1903
|
+
}
|
|
1904
|
+
return customHeaders;
|
|
1115
1905
|
}
|
|
1116
|
-
async function* readStreamLines(
|
|
1906
|
+
async function* readStreamLines(reader, abortSignal) {
|
|
1117
1907
|
try {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1908
|
+
const decoder = new TextDecoder();
|
|
1909
|
+
let buffer = "";
|
|
1910
|
+
while (true) {
|
|
1911
|
+
if (abortSignal?.aborted) break;
|
|
1912
|
+
const { done, value } = await reader.read();
|
|
1913
|
+
if (done) break;
|
|
1914
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1915
|
+
const lines = buffer.split("\n");
|
|
1916
|
+
buffer = lines.pop() || "";
|
|
1917
|
+
for (const line of lines) if (line.trim()) yield line;
|
|
1126
1918
|
}
|
|
1127
|
-
|
|
1919
|
+
if (buffer.trim()) yield buffer;
|
|
1128
1920
|
} finally {
|
|
1129
|
-
|
|
1921
|
+
reader.releaseLock();
|
|
1130
1922
|
}
|
|
1131
1923
|
}
|
|
1132
|
-
function normalizeConnectionAdapter(
|
|
1133
|
-
if (!
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1924
|
+
function normalizeConnectionAdapter(connection) {
|
|
1925
|
+
if (!connection) throw new Error("Connection adapter is required");
|
|
1926
|
+
const hasConnect = "connect" in connection;
|
|
1927
|
+
const hasSubscribe = "subscribe" in connection;
|
|
1928
|
+
const hasSend = "send" in connection;
|
|
1929
|
+
if (hasConnect && (hasSubscribe || hasSend)) throw new Error("Connection adapter must provide either connect or both subscribe and send, not both modes");
|
|
1930
|
+
if (hasSubscribe && hasSend) return {
|
|
1931
|
+
subscribe: connection.subscribe.bind(connection),
|
|
1932
|
+
send: connection.send.bind(connection)
|
|
1139
1933
|
};
|
|
1140
|
-
if (!
|
|
1141
|
-
let
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1934
|
+
if (!hasConnect) throw new Error("Connection adapter must provide either connect or both subscribe and send");
|
|
1935
|
+
let activeBuffer = [];
|
|
1936
|
+
let activeWaiters = [];
|
|
1937
|
+
function push(chunk) {
|
|
1938
|
+
const waiter = activeWaiters.shift();
|
|
1939
|
+
if (waiter) waiter(chunk);
|
|
1940
|
+
else activeBuffer.push(chunk);
|
|
1145
1941
|
}
|
|
1146
1942
|
return {
|
|
1147
|
-
subscribe(
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1943
|
+
subscribe(abortSignal) {
|
|
1944
|
+
const myBuffer = activeBuffer.splice(0);
|
|
1945
|
+
const myWaiters = [];
|
|
1946
|
+
activeBuffer = myBuffer;
|
|
1947
|
+
activeWaiters = myWaiters;
|
|
1948
|
+
return (async function* () {
|
|
1949
|
+
while (!abortSignal?.aborted) {
|
|
1950
|
+
let chunk;
|
|
1951
|
+
if (myBuffer.length > 0) chunk = myBuffer.shift();
|
|
1952
|
+
else chunk = await new Promise((resolve) => {
|
|
1953
|
+
const onAbort = () => resolve(null);
|
|
1954
|
+
myWaiters.push((c) => {
|
|
1955
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
1956
|
+
resolve(c);
|
|
1957
|
+
});
|
|
1958
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1959
|
+
});
|
|
1960
|
+
if (chunk !== null) yield chunk;
|
|
1158
1961
|
}
|
|
1159
1962
|
})();
|
|
1160
1963
|
},
|
|
1161
|
-
async send(
|
|
1162
|
-
let
|
|
1964
|
+
async send(messages, data, abortSignal) {
|
|
1965
|
+
let hasTerminalEvent = false;
|
|
1163
1966
|
try {
|
|
1164
|
-
|
|
1165
|
-
for await (
|
|
1166
|
-
|
|
1967
|
+
const stream2 = connection.connect(messages, data, abortSignal);
|
|
1968
|
+
for await (const chunk of stream2) {
|
|
1969
|
+
if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") hasTerminalEvent = true;
|
|
1970
|
+
push(chunk);
|
|
1971
|
+
}
|
|
1972
|
+
if (!abortSignal?.aborted && !hasTerminalEvent) push({
|
|
1167
1973
|
type: "RUN_FINISHED",
|
|
1168
1974
|
runId: `run-${Date.now()}`,
|
|
1169
1975
|
model: "connect-wrapper",
|
|
1170
1976
|
timestamp: Date.now(),
|
|
1171
1977
|
finishReason: "stop"
|
|
1172
1978
|
});
|
|
1173
|
-
} catch (
|
|
1174
|
-
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
if (!abortSignal?.aborted && !hasTerminalEvent) push({
|
|
1175
1981
|
type: "RUN_ERROR",
|
|
1176
1982
|
timestamp: Date.now(),
|
|
1177
|
-
message:
|
|
1178
|
-
error: { message:
|
|
1179
|
-
})
|
|
1983
|
+
message: err instanceof Error ? err.message : "Unknown error in connect()",
|
|
1984
|
+
error: { message: err instanceof Error ? err.message : "Unknown error in connect()" }
|
|
1985
|
+
});
|
|
1986
|
+
throw err;
|
|
1180
1987
|
}
|
|
1181
1988
|
}
|
|
1182
1989
|
};
|
|
1183
1990
|
}
|
|
1184
|
-
function fetchServerSentEvents(
|
|
1185
|
-
return { async *connect(
|
|
1186
|
-
|
|
1991
|
+
function fetchServerSentEvents(url, options = {}) {
|
|
1992
|
+
return { async *connect(messages, data, abortSignal) {
|
|
1993
|
+
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
1994
|
+
const resolvedOptions = typeof options === "function" ? await options() : options;
|
|
1995
|
+
const requestHeaders = {
|
|
1187
1996
|
"Content-Type": "application/json",
|
|
1188
|
-
...mergeHeaders(
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1997
|
+
...mergeHeaders(resolvedOptions.headers)
|
|
1998
|
+
};
|
|
1999
|
+
const requestBody = {
|
|
2000
|
+
messages,
|
|
2001
|
+
data,
|
|
2002
|
+
...resolvedOptions.body
|
|
2003
|
+
};
|
|
2004
|
+
const response = await (resolvedOptions.fetchClient ?? fetch)(resolvedUrl, {
|
|
1194
2005
|
method: "POST",
|
|
1195
|
-
headers:
|
|
1196
|
-
body: JSON.stringify(
|
|
1197
|
-
credentials:
|
|
1198
|
-
signal:
|
|
2006
|
+
headers: requestHeaders,
|
|
2007
|
+
body: JSON.stringify(requestBody),
|
|
2008
|
+
credentials: resolvedOptions.credentials || "same-origin",
|
|
2009
|
+
signal: abortSignal || resolvedOptions.signal
|
|
1199
2010
|
});
|
|
1200
|
-
if (!
|
|
1201
|
-
|
|
1202
|
-
if (!
|
|
1203
|
-
for await (
|
|
1204
|
-
|
|
1205
|
-
if (
|
|
2011
|
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);
|
|
2012
|
+
const reader = response.body?.getReader();
|
|
2013
|
+
if (!reader) throw new Error("Response body is not readable");
|
|
2014
|
+
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
2015
|
+
const data2 = line.startsWith("data: ") ? line.slice(6) : line;
|
|
2016
|
+
if (data2 === "[DONE]") {
|
|
1206
2017
|
console.warn("[@tanstack/ai-client] Received [DONE] sentinel. This is deprecated — upgrade your @tanstack/ai server package. RUN_FINISHED is the stream terminator.");
|
|
1207
2018
|
continue;
|
|
1208
2019
|
}
|
|
1209
2020
|
try {
|
|
1210
|
-
yield JSON.parse(
|
|
1211
|
-
} catch {
|
|
1212
|
-
console.warn("Failed to parse SSE chunk:",
|
|
2021
|
+
yield JSON.parse(data2);
|
|
2022
|
+
} catch (parseError) {
|
|
2023
|
+
console.warn("Failed to parse SSE chunk:", data2);
|
|
1213
2024
|
}
|
|
1214
2025
|
}
|
|
1215
2026
|
} };
|
|
@@ -1217,176 +2028,315 @@ function fetchServerSentEvents(e, t = {}) {
|
|
|
1217
2028
|
//#endregion
|
|
1218
2029
|
//#region ../../node_modules/.pnpm/@tanstack+ai-client@0.8.0/node_modules/@tanstack/ai-client/dist/esm/chat-client.js
|
|
1219
2030
|
var ChatClient = class {
|
|
1220
|
-
constructor(
|
|
1221
|
-
|
|
2031
|
+
constructor(options) {
|
|
2032
|
+
this.body = {};
|
|
2033
|
+
this.pendingMessageBody = void 0;
|
|
2034
|
+
this.isLoading = false;
|
|
2035
|
+
this.isSubscribed = false;
|
|
2036
|
+
this.error = void 0;
|
|
2037
|
+
this.status = "ready";
|
|
2038
|
+
this.connectionStatus = "disconnected";
|
|
2039
|
+
this.abortController = null;
|
|
2040
|
+
this.currentStreamId = null;
|
|
2041
|
+
this.currentMessageId = null;
|
|
2042
|
+
this.postStreamActions = [];
|
|
2043
|
+
this.pendingToolExecutions = /* @__PURE__ */ new Map();
|
|
2044
|
+
this.continuationPending = false;
|
|
2045
|
+
this.subscriptionAbortController = null;
|
|
2046
|
+
this.processingResolve = null;
|
|
2047
|
+
this.errorReportedGeneration = null;
|
|
2048
|
+
this.streamGeneration = 0;
|
|
2049
|
+
this.continuationSkipped = false;
|
|
2050
|
+
this.draining = false;
|
|
2051
|
+
this.sessionGenerating = false;
|
|
2052
|
+
this.activeRunIds = /* @__PURE__ */ new Set();
|
|
2053
|
+
this.uniqueId = options.id || this.generateUniqueId("chat");
|
|
2054
|
+
this.body = options.body || {};
|
|
2055
|
+
this.connection = normalizeConnectionAdapter(options.connection);
|
|
2056
|
+
this.events = new DefaultChatClientEventEmitter(this.uniqueId);
|
|
2057
|
+
this.clientToolsRef = { current: /* @__PURE__ */ new Map() };
|
|
2058
|
+
if (options.tools) for (const tool of options.tools) this.clientToolsRef.current.set(tool.name, tool);
|
|
1222
2059
|
this.callbacksRef = { current: {
|
|
1223
|
-
onResponse:
|
|
1224
|
-
onChunk:
|
|
1225
|
-
onFinish:
|
|
1226
|
-
onError:
|
|
1227
|
-
onMessagesChange:
|
|
1228
|
-
onLoadingChange:
|
|
1229
|
-
onErrorChange:
|
|
1230
|
-
onStatusChange:
|
|
1231
|
-
onSubscriptionChange:
|
|
1232
|
-
onConnectionStatusChange:
|
|
1233
|
-
onSessionGeneratingChange:
|
|
1234
|
-
onCustomEvent:
|
|
1235
|
-
} }
|
|
1236
|
-
|
|
1237
|
-
|
|
2060
|
+
onResponse: options.onResponse || (() => {}),
|
|
2061
|
+
onChunk: options.onChunk || (() => {}),
|
|
2062
|
+
onFinish: options.onFinish || (() => {}),
|
|
2063
|
+
onError: options.onError || (() => {}),
|
|
2064
|
+
onMessagesChange: options.onMessagesChange || (() => {}),
|
|
2065
|
+
onLoadingChange: options.onLoadingChange || (() => {}),
|
|
2066
|
+
onErrorChange: options.onErrorChange || (() => {}),
|
|
2067
|
+
onStatusChange: options.onStatusChange || (() => {}),
|
|
2068
|
+
onSubscriptionChange: options.onSubscriptionChange || (() => {}),
|
|
2069
|
+
onConnectionStatusChange: options.onConnectionStatusChange || (() => {}),
|
|
2070
|
+
onSessionGeneratingChange: options.onSessionGeneratingChange || (() => {}),
|
|
2071
|
+
onCustomEvent: options.onCustomEvent || (() => {})
|
|
2072
|
+
} };
|
|
2073
|
+
this.processor = new StreamProcessor({
|
|
2074
|
+
chunkStrategy: options.streamProcessor?.chunkStrategy,
|
|
2075
|
+
initialMessages: options.initialMessages,
|
|
1238
2076
|
events: {
|
|
1239
|
-
onMessagesChange: (
|
|
1240
|
-
this.callbacksRef.current.onMessagesChange(
|
|
2077
|
+
onMessagesChange: (messages) => {
|
|
2078
|
+
this.callbacksRef.current.onMessagesChange(messages);
|
|
1241
2079
|
},
|
|
1242
2080
|
onStreamStart: () => {
|
|
1243
2081
|
this.setStatus("streaming");
|
|
1244
|
-
|
|
1245
|
-
if (!
|
|
1246
|
-
|
|
1247
|
-
|
|
2082
|
+
const assistantMessageId = this.processor.getCurrentAssistantMessageId();
|
|
2083
|
+
if (!assistantMessageId) return;
|
|
2084
|
+
const assistantMessage = this.processor.getMessages().find((m) => m.id === assistantMessageId);
|
|
2085
|
+
if (assistantMessage) {
|
|
2086
|
+
this.currentMessageId = assistantMessage.id;
|
|
2087
|
+
this.events.messageAppended(assistantMessage, this.currentStreamId || void 0);
|
|
2088
|
+
}
|
|
1248
2089
|
},
|
|
1249
|
-
onStreamEnd: (
|
|
1250
|
-
this.callbacksRef.current.onFinish(
|
|
2090
|
+
onStreamEnd: (message) => {
|
|
2091
|
+
this.callbacksRef.current.onFinish(message);
|
|
2092
|
+
this.setStatus("ready");
|
|
2093
|
+
this.resolveProcessing();
|
|
1251
2094
|
},
|
|
1252
|
-
onError: (
|
|
1253
|
-
this.reportStreamError(
|
|
2095
|
+
onError: (error) => {
|
|
2096
|
+
this.reportStreamError(error);
|
|
1254
2097
|
},
|
|
1255
|
-
onTextUpdate: (
|
|
1256
|
-
this.currentStreamId
|
|
2098
|
+
onTextUpdate: (messageId, content) => {
|
|
2099
|
+
if (this.currentStreamId) this.events.textUpdated(this.currentStreamId, messageId, content);
|
|
1257
2100
|
},
|
|
1258
|
-
onThinkingUpdate: (
|
|
1259
|
-
this.currentStreamId
|
|
2101
|
+
onThinkingUpdate: (messageId, content) => {
|
|
2102
|
+
if (this.currentStreamId) this.events.thinkingUpdated(this.currentStreamId, messageId, content);
|
|
1260
2103
|
},
|
|
1261
|
-
onToolCallStateChange: (
|
|
1262
|
-
|
|
1263
|
-
this.currentStreamId
|
|
2104
|
+
onToolCallStateChange: (messageId, toolCallId, state, args) => {
|
|
2105
|
+
const toolName = (this.processor.getMessages().find((m) => m.id === messageId)?.parts.find((p) => p.type === "tool-call" && p.id === toolCallId))?.name || "unknown";
|
|
2106
|
+
if (this.currentStreamId) this.events.toolCallStateChanged(this.currentStreamId, messageId, toolCallId, toolName, state, args);
|
|
1264
2107
|
},
|
|
1265
|
-
onToolCall: (
|
|
1266
|
-
|
|
1267
|
-
if (
|
|
1268
|
-
|
|
2108
|
+
onToolCall: (args) => {
|
|
2109
|
+
const executeFunc = this.clientToolsRef.current.get(args.toolName)?.execute;
|
|
2110
|
+
if (executeFunc) {
|
|
2111
|
+
const executionPromise = (async () => {
|
|
1269
2112
|
try {
|
|
1270
|
-
|
|
2113
|
+
const output = await executeFunc(args.input);
|
|
1271
2114
|
await this.addToolResult({
|
|
1272
|
-
toolCallId:
|
|
1273
|
-
tool:
|
|
1274
|
-
output
|
|
2115
|
+
toolCallId: args.toolCallId,
|
|
2116
|
+
tool: args.toolName,
|
|
2117
|
+
output,
|
|
1275
2118
|
state: "output-available"
|
|
1276
2119
|
});
|
|
1277
|
-
} catch (
|
|
2120
|
+
} catch (error) {
|
|
1278
2121
|
await this.addToolResult({
|
|
1279
|
-
toolCallId:
|
|
1280
|
-
tool:
|
|
2122
|
+
toolCallId: args.toolCallId,
|
|
2123
|
+
tool: args.toolName,
|
|
1281
2124
|
output: null,
|
|
1282
2125
|
state: "output-error",
|
|
1283
|
-
errorText:
|
|
2126
|
+
errorText: error.message
|
|
1284
2127
|
});
|
|
1285
2128
|
} finally {
|
|
1286
|
-
this.pendingToolExecutions.delete(
|
|
2129
|
+
this.pendingToolExecutions.delete(args.toolCallId);
|
|
1287
2130
|
}
|
|
1288
2131
|
})();
|
|
1289
|
-
this.pendingToolExecutions.set(
|
|
2132
|
+
this.pendingToolExecutions.set(args.toolCallId, executionPromise);
|
|
1290
2133
|
}
|
|
1291
2134
|
},
|
|
1292
|
-
onApprovalRequest: (
|
|
1293
|
-
this.currentStreamId
|
|
2135
|
+
onApprovalRequest: (args) => {
|
|
2136
|
+
if (this.currentStreamId) this.events.approvalRequested(this.currentStreamId, this.currentMessageId || "", args.toolCallId, args.toolName, args.input, args.approvalId);
|
|
1294
2137
|
},
|
|
1295
|
-
onCustomEvent: (
|
|
1296
|
-
this.callbacksRef.current.onCustomEvent(
|
|
2138
|
+
onCustomEvent: (eventType, data, context) => {
|
|
2139
|
+
this.callbacksRef.current.onCustomEvent(eventType, data, context);
|
|
1297
2140
|
}
|
|
1298
2141
|
}
|
|
1299
|
-
})
|
|
2142
|
+
});
|
|
2143
|
+
this.events.clientCreated(this.processor.getMessages().length);
|
|
1300
2144
|
}
|
|
1301
|
-
generateUniqueId(
|
|
1302
|
-
return `${
|
|
2145
|
+
generateUniqueId(prefix) {
|
|
2146
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
1303
2147
|
}
|
|
1304
|
-
setIsLoading(
|
|
1305
|
-
this.isLoading =
|
|
2148
|
+
setIsLoading(isLoading) {
|
|
2149
|
+
this.isLoading = isLoading;
|
|
2150
|
+
this.callbacksRef.current.onLoadingChange(isLoading);
|
|
2151
|
+
this.events.loadingChanged(isLoading);
|
|
1306
2152
|
}
|
|
1307
|
-
setStatus(
|
|
1308
|
-
this.status =
|
|
2153
|
+
setStatus(status) {
|
|
2154
|
+
this.status = status;
|
|
2155
|
+
this.callbacksRef.current.onStatusChange(status);
|
|
1309
2156
|
}
|
|
1310
|
-
setIsSubscribed(
|
|
1311
|
-
this.isSubscribed =
|
|
2157
|
+
setIsSubscribed(isSubscribed) {
|
|
2158
|
+
this.isSubscribed = isSubscribed;
|
|
2159
|
+
this.callbacksRef.current.onSubscriptionChange(isSubscribed);
|
|
1312
2160
|
}
|
|
1313
|
-
setConnectionStatus(
|
|
1314
|
-
this.connectionStatus =
|
|
2161
|
+
setConnectionStatus(status) {
|
|
2162
|
+
this.connectionStatus = status;
|
|
2163
|
+
this.callbacksRef.current.onConnectionStatusChange(status);
|
|
1315
2164
|
}
|
|
1316
|
-
setSessionGenerating(
|
|
1317
|
-
|
|
2165
|
+
setSessionGenerating(isGenerating) {
|
|
2166
|
+
if (this.sessionGenerating === isGenerating) return;
|
|
2167
|
+
this.sessionGenerating = isGenerating;
|
|
2168
|
+
this.callbacksRef.current.onSessionGeneratingChange(isGenerating);
|
|
1318
2169
|
}
|
|
1319
2170
|
resetSessionGenerating() {
|
|
1320
|
-
this.activeRunIds.clear()
|
|
2171
|
+
this.activeRunIds.clear();
|
|
2172
|
+
this.setSessionGenerating(false);
|
|
1321
2173
|
}
|
|
1322
|
-
setError(
|
|
1323
|
-
this.error =
|
|
2174
|
+
setError(error) {
|
|
2175
|
+
this.error = error;
|
|
2176
|
+
this.callbacksRef.current.onErrorChange(error);
|
|
2177
|
+
this.events.errorChanged(error?.message || null);
|
|
1324
2178
|
}
|
|
1325
2179
|
abortSubscriptionLoop() {
|
|
1326
|
-
this.subscriptionAbortController?.abort()
|
|
2180
|
+
this.subscriptionAbortController?.abort();
|
|
2181
|
+
this.subscriptionAbortController = null;
|
|
1327
2182
|
}
|
|
1328
2183
|
resolveProcessing() {
|
|
1329
|
-
this.processingResolve?.()
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
this.
|
|
2184
|
+
this.processingResolve?.();
|
|
2185
|
+
this.processingResolve = null;
|
|
2186
|
+
}
|
|
2187
|
+
cancelInFlightStream(options) {
|
|
2188
|
+
this.abortController?.abort();
|
|
2189
|
+
this.abortController = null;
|
|
2190
|
+
if (options?.abortSubscription) this.abortSubscriptionLoop();
|
|
2191
|
+
this.resolveProcessing();
|
|
2192
|
+
this.setIsLoading(false);
|
|
2193
|
+
if (options?.setReadyStatus) this.setStatus("ready");
|
|
2194
|
+
}
|
|
2195
|
+
reportStreamError(error) {
|
|
2196
|
+
const alreadyReported = this.errorReportedGeneration === this.streamGeneration;
|
|
2197
|
+
this.setError(error);
|
|
2198
|
+
if (this.isLoading || this.status === "submitted" || this.status === "streaming") this.setStatus("error");
|
|
2199
|
+
if (!alreadyReported) {
|
|
2200
|
+
this.errorReportedGeneration = this.streamGeneration;
|
|
2201
|
+
this.callbacksRef.current.onError(error);
|
|
2202
|
+
}
|
|
1337
2203
|
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Start the background subscription loop.
|
|
2206
|
+
*/
|
|
1338
2207
|
startSubscription() {
|
|
1339
2208
|
this.subscriptionAbortController = new AbortController();
|
|
1340
|
-
|
|
1341
|
-
this.consumeSubscription(
|
|
1342
|
-
|
|
2209
|
+
const signal = this.subscriptionAbortController.signal;
|
|
2210
|
+
this.consumeSubscription(signal).catch((err) => {
|
|
2211
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
2212
|
+
this.setConnectionStatus("error");
|
|
2213
|
+
this.resetSessionGenerating();
|
|
2214
|
+
this.setIsSubscribed(false);
|
|
2215
|
+
this.reportStreamError(err);
|
|
2216
|
+
}
|
|
2217
|
+
this.resolveProcessing();
|
|
1343
2218
|
}).finally(() => {
|
|
1344
|
-
this.subscriptionAbortController?.signal
|
|
2219
|
+
if (this.subscriptionAbortController?.signal !== signal) return;
|
|
2220
|
+
this.subscriptionAbortController = null;
|
|
2221
|
+
if (!signal.aborted && this.isSubscribed) {
|
|
2222
|
+
this.setIsSubscribed(false);
|
|
2223
|
+
if (this.connectionStatus !== "error") this.setConnectionStatus("disconnected");
|
|
2224
|
+
}
|
|
1345
2225
|
});
|
|
1346
2226
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
2227
|
+
/**
|
|
2228
|
+
* Consume chunks from the connection subscription.
|
|
2229
|
+
*/
|
|
2230
|
+
async consumeSubscription(signal) {
|
|
2231
|
+
const stream = this.connection.subscribe(signal);
|
|
2232
|
+
for await (const chunk of stream) {
|
|
2233
|
+
if (signal.aborted) break;
|
|
2234
|
+
if (this.connectionStatus === "connecting") this.setConnectionStatus("connected");
|
|
2235
|
+
this.callbacksRef.current.onChunk(chunk);
|
|
2236
|
+
this.processor.processChunk(chunk);
|
|
2237
|
+
if (chunk.type === "RUN_STARTED") {
|
|
2238
|
+
this.activeRunIds.add(chunk.runId);
|
|
2239
|
+
this.setSessionGenerating(true);
|
|
1354
2240
|
}
|
|
1355
|
-
|
|
2241
|
+
if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
|
|
2242
|
+
const runId = chunk.type === "RUN_FINISHED" ? chunk.runId : void 0;
|
|
2243
|
+
if (runId) this.activeRunIds.delete(runId);
|
|
2244
|
+
else if (chunk.type === "RUN_ERROR") this.activeRunIds.clear();
|
|
2245
|
+
this.setSessionGenerating(this.activeRunIds.size > 0);
|
|
2246
|
+
this.resolveProcessing();
|
|
2247
|
+
}
|
|
2248
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1356
2249
|
}
|
|
1357
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Ensure subscription loop is running, starting it if needed.
|
|
2253
|
+
*/
|
|
1358
2254
|
ensureSubscription() {
|
|
1359
2255
|
if (!this.isSubscribed) {
|
|
1360
2256
|
this.subscribe();
|
|
1361
2257
|
return;
|
|
1362
2258
|
}
|
|
1363
|
-
(!this.subscriptionAbortController || this.subscriptionAbortController.signal.aborted)
|
|
2259
|
+
if (!this.subscriptionAbortController || this.subscriptionAbortController.signal.aborted) this.subscribe({ restart: true });
|
|
1364
2260
|
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Create a promise that resolves when onStreamEnd fires.
|
|
2263
|
+
* Used by streamResponse to await processing completion.
|
|
2264
|
+
*/
|
|
1365
2265
|
waitForProcessing() {
|
|
1366
|
-
|
|
1367
|
-
|
|
2266
|
+
this.resolveProcessing();
|
|
2267
|
+
return new Promise((resolve) => {
|
|
2268
|
+
this.processingResolve = resolve;
|
|
1368
2269
|
});
|
|
1369
2270
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
2271
|
+
/**
|
|
2272
|
+
* Send a message and stream the response.
|
|
2273
|
+
* Supports both simple string content and multimodal content (images, audio, video, documents).
|
|
2274
|
+
*
|
|
2275
|
+
* @param content - The message content. Can be:
|
|
2276
|
+
* - A simple string for text-only messages
|
|
2277
|
+
* - A MultimodalContent object with content array and optional custom ID
|
|
2278
|
+
* @param body - Optional body parameters to merge with the client's base body for this request.
|
|
2279
|
+
* Uses shallow merge with per-message body taking priority.
|
|
2280
|
+
*
|
|
2281
|
+
* @example
|
|
2282
|
+
* ```ts
|
|
2283
|
+
* // Simple text message
|
|
2284
|
+
* await client.sendMessage('Hello!')
|
|
2285
|
+
*
|
|
2286
|
+
* // Text message with custom body params
|
|
2287
|
+
* await client.sendMessage('Hello!', { temperature: 0.7 })
|
|
2288
|
+
*
|
|
2289
|
+
* // Multimodal message with image
|
|
2290
|
+
* await client.sendMessage({
|
|
2291
|
+
* content: [
|
|
2292
|
+
* { type: 'text', content: 'What is in this image?' },
|
|
2293
|
+
* { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }
|
|
2294
|
+
* ]
|
|
2295
|
+
* })
|
|
2296
|
+
*
|
|
2297
|
+
* // Multimodal message with custom ID and body params
|
|
2298
|
+
* await client.sendMessage(
|
|
2299
|
+
* {
|
|
2300
|
+
* content: [
|
|
2301
|
+
* { type: 'text', content: 'Describe this audio' },
|
|
2302
|
+
* { type: 'audio', source: { type: 'data', value: 'base64...' } }
|
|
2303
|
+
* ],
|
|
2304
|
+
* id: 'custom-message-id'
|
|
2305
|
+
* },
|
|
2306
|
+
* { model: 'gpt-4-audio' }
|
|
2307
|
+
* )
|
|
2308
|
+
* ```
|
|
2309
|
+
*/
|
|
2310
|
+
async sendMessage(content, body) {
|
|
2311
|
+
if (typeof content === "string" && !content.trim() || this.isLoading) return;
|
|
2312
|
+
const normalizedContent = this.normalizeMessageInput(content);
|
|
2313
|
+
this.pendingMessageBody = body;
|
|
2314
|
+
const userMessage = this.processor.addUserMessage(normalizedContent.content, normalizedContent.id);
|
|
2315
|
+
this.events.messageSent(userMessage.id, normalizedContent.content);
|
|
2316
|
+
await this.streamResponse();
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Normalize the message input to extract content and optional id.
|
|
2320
|
+
* Trims string content automatically.
|
|
2321
|
+
*/
|
|
2322
|
+
normalizeMessageInput(input) {
|
|
2323
|
+
if (typeof input === "string") return { content: input.trim() };
|
|
2324
|
+
return {
|
|
2325
|
+
content: input.content,
|
|
2326
|
+
id: input.id
|
|
1381
2327
|
};
|
|
1382
2328
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
2329
|
+
/**
|
|
2330
|
+
* Append a message and stream the response
|
|
2331
|
+
*/
|
|
2332
|
+
async append(message) {
|
|
2333
|
+
const normalizedMessage = normalizeToUIMessage(message, generateMessageId);
|
|
2334
|
+
if (normalizedMessage.role === "system") return;
|
|
2335
|
+
const uiMessage = normalizedMessage;
|
|
2336
|
+
this.events.messageAppended(uiMessage);
|
|
2337
|
+
const messages = this.processor.getMessages();
|
|
2338
|
+
this.processor.setMessages([...messages, uiMessage]);
|
|
2339
|
+
if (this.isLoading) {
|
|
1390
2340
|
this.queuePostStreamAction(async () => {
|
|
1391
2341
|
await this.streamResponse();
|
|
1392
2342
|
});
|
|
@@ -1394,375 +2344,545 @@ var ChatClient = class {
|
|
|
1394
2344
|
}
|
|
1395
2345
|
await this.streamResponse();
|
|
1396
2346
|
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Stream a response from the LLM.
|
|
2349
|
+
* Returns true if the stream completed successfully, false on abort or error.
|
|
2350
|
+
*/
|
|
1397
2351
|
async streamResponse() {
|
|
1398
|
-
if (this.isLoading) return
|
|
1399
|
-
|
|
1400
|
-
this.setIsLoading(
|
|
1401
|
-
|
|
2352
|
+
if (this.isLoading) return false;
|
|
2353
|
+
const generation = ++this.streamGeneration;
|
|
2354
|
+
this.setIsLoading(true);
|
|
2355
|
+
this.setStatus("submitted");
|
|
2356
|
+
this.setError(void 0);
|
|
2357
|
+
this.errorReportedGeneration = null;
|
|
2358
|
+
this.abortController = new AbortController();
|
|
2359
|
+
this.pendingToolExecutions.clear();
|
|
2360
|
+
let streamCompletedSuccessfully = false;
|
|
1402
2361
|
try {
|
|
1403
|
-
|
|
2362
|
+
const messages = this.processor.getMessages();
|
|
1404
2363
|
await this.callbacksRef.current.onResponse();
|
|
1405
|
-
|
|
2364
|
+
const mergedBody = {
|
|
1406
2365
|
...this.body,
|
|
1407
2366
|
...this.pendingMessageBody,
|
|
1408
2367
|
conversationId: this.uniqueId
|
|
1409
2368
|
};
|
|
1410
|
-
this.pendingMessageBody = void 0
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
this.
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
2369
|
+
this.pendingMessageBody = void 0;
|
|
2370
|
+
this.currentStreamId = this.generateUniqueId("stream");
|
|
2371
|
+
this.currentMessageId = null;
|
|
2372
|
+
this.processor.prepareAssistantMessage();
|
|
2373
|
+
this.ensureSubscription();
|
|
2374
|
+
const processingComplete = this.waitForProcessing();
|
|
2375
|
+
await this.connection.send(messages, mergedBody, this.abortController.signal);
|
|
2376
|
+
await processingComplete;
|
|
2377
|
+
if (generation !== this.streamGeneration) return false;
|
|
2378
|
+
if (this.status === "error") return false;
|
|
2379
|
+
if (this.pendingToolExecutions.size > 0) await Promise.all(this.pendingToolExecutions.values());
|
|
2380
|
+
this.processor.finalizeStream();
|
|
2381
|
+
streamCompletedSuccessfully = true;
|
|
2382
|
+
} catch (err) {
|
|
2383
|
+
if (err instanceof Error) {
|
|
2384
|
+
if (err.name === "AbortError") return false;
|
|
2385
|
+
if (generation === this.streamGeneration) this.reportStreamError(err);
|
|
1418
2386
|
}
|
|
1419
2387
|
} finally {
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
2388
|
+
if (generation === this.streamGeneration) {
|
|
2389
|
+
this.currentStreamId = null;
|
|
2390
|
+
this.currentMessageId = null;
|
|
2391
|
+
this.abortController = null;
|
|
2392
|
+
this.setIsLoading(false);
|
|
2393
|
+
this.pendingMessageBody = void 0;
|
|
2394
|
+
await this.drainPostStreamActions();
|
|
2395
|
+
if (streamCompletedSuccessfully) {
|
|
2396
|
+
const lastPart = this.processor.getMessages().at(-1)?.parts.at(-1);
|
|
2397
|
+
const { finishReason } = this.processor.getState();
|
|
2398
|
+
if (lastPart?.type === "tool-result" && finishReason !== "stop" && this.shouldAutoSend()) try {
|
|
2399
|
+
await this.checkForContinuation();
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
console.error("Failed to continue flow after tool result:", error);
|
|
2402
|
+
}
|
|
1426
2403
|
}
|
|
1427
2404
|
}
|
|
1428
2405
|
}
|
|
1429
|
-
return
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2406
|
+
return streamCompletedSuccessfully;
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Start the client subscription loop.
|
|
2410
|
+
* This controls the connection lifecycle independently from request lifecycle.
|
|
2411
|
+
*/
|
|
2412
|
+
subscribe(options) {
|
|
2413
|
+
const restart = options?.restart === true;
|
|
2414
|
+
if (this.isSubscribed && !restart) return;
|
|
2415
|
+
if (this.isSubscribed && restart) this.abortSubscriptionLoop();
|
|
2416
|
+
this.setIsSubscribed(true);
|
|
2417
|
+
this.setConnectionStatus("connecting");
|
|
2418
|
+
this.startSubscription();
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Unsubscribe and fully tear down live behavior.
|
|
2422
|
+
* This aborts an in-flight request and the subscription loop.
|
|
2423
|
+
*/
|
|
1435
2424
|
unsubscribe() {
|
|
1436
2425
|
this.cancelInFlightStream({
|
|
1437
|
-
setReadyStatus:
|
|
1438
|
-
abortSubscription:
|
|
1439
|
-
})
|
|
2426
|
+
setReadyStatus: true,
|
|
2427
|
+
abortSubscription: true
|
|
2428
|
+
});
|
|
2429
|
+
this.resetSessionGenerating();
|
|
2430
|
+
this.setIsSubscribed(false);
|
|
2431
|
+
this.setConnectionStatus("disconnected");
|
|
1440
2432
|
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Reload the last assistant message
|
|
2435
|
+
*/
|
|
1441
2436
|
async reload() {
|
|
1442
|
-
|
|
1443
|
-
if (
|
|
1444
|
-
|
|
1445
|
-
|
|
2437
|
+
const messages = this.processor.getMessages();
|
|
2438
|
+
if (messages.length === 0) return;
|
|
2439
|
+
const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
|
|
2440
|
+
if (lastUserMessageIndex === -1) return;
|
|
2441
|
+
if (this.isLoading) this.cancelInFlightStream();
|
|
2442
|
+
this.events.reloaded(lastUserMessageIndex);
|
|
2443
|
+
this.processor.removeMessagesAfter(lastUserMessageIndex);
|
|
2444
|
+
await this.streamResponse();
|
|
1446
2445
|
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Stop the current stream
|
|
2448
|
+
*/
|
|
1447
2449
|
stop() {
|
|
1448
|
-
this.cancelInFlightStream({ setReadyStatus:
|
|
2450
|
+
this.cancelInFlightStream({ setReadyStatus: true });
|
|
2451
|
+
this.events.stopped();
|
|
1449
2452
|
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Clear all messages
|
|
2455
|
+
*/
|
|
1450
2456
|
clear() {
|
|
1451
|
-
this.processor.clearMessages()
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
2457
|
+
this.processor.clearMessages();
|
|
2458
|
+
this.setError(void 0);
|
|
2459
|
+
this.events.messagesCleared();
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Add the result of a client-side tool execution
|
|
2463
|
+
*/
|
|
2464
|
+
async addToolResult(result) {
|
|
2465
|
+
this.events.toolResultAdded(result.toolCallId, result.tool, result.output, result.state || "output-available");
|
|
2466
|
+
this.processor.addToolResult(result.toolCallId, result.output, result.errorText);
|
|
2467
|
+
if (this.isLoading) {
|
|
1455
2468
|
this.queuePostStreamAction(() => this.checkForContinuation());
|
|
1456
2469
|
return;
|
|
1457
2470
|
}
|
|
1458
2471
|
await this.checkForContinuation();
|
|
1459
2472
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
2473
|
+
/**
|
|
2474
|
+
* Respond to a tool approval request
|
|
2475
|
+
*/
|
|
2476
|
+
async addToolApprovalResponse(response) {
|
|
2477
|
+
const messages = this.processor.getMessages();
|
|
2478
|
+
let foundToolCallId;
|
|
2479
|
+
for (const msg of messages) {
|
|
2480
|
+
const toolCallPart = msg.parts.find((p) => p.type === "tool-call" && p.approval?.id === response.id);
|
|
2481
|
+
if (toolCallPart) {
|
|
2482
|
+
foundToolCallId = toolCallPart.id;
|
|
1466
2483
|
break;
|
|
1467
2484
|
}
|
|
1468
2485
|
}
|
|
1469
|
-
if (
|
|
2486
|
+
if (foundToolCallId) this.events.toolApprovalResponded(response.id, foundToolCallId, response.approved);
|
|
2487
|
+
this.processor.addToolApprovalResponse(response.id, response.approved);
|
|
2488
|
+
if (this.isLoading) {
|
|
1470
2489
|
this.queuePostStreamAction(() => this.checkForContinuation());
|
|
1471
2490
|
return;
|
|
1472
2491
|
}
|
|
1473
2492
|
await this.checkForContinuation();
|
|
1474
2493
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
2494
|
+
/**
|
|
2495
|
+
* Queue an action to be executed after the current stream ends
|
|
2496
|
+
*/
|
|
2497
|
+
queuePostStreamAction(action) {
|
|
2498
|
+
this.postStreamActions.push(action);
|
|
1477
2499
|
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Drain and execute all queued post-stream actions
|
|
2502
|
+
*/
|
|
1478
2503
|
async drainPostStreamActions() {
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
2504
|
+
if (this.draining) return;
|
|
2505
|
+
this.draining = true;
|
|
2506
|
+
try {
|
|
2507
|
+
while (this.postStreamActions.length > 0) await this.postStreamActions.shift()();
|
|
2508
|
+
} finally {
|
|
2509
|
+
this.draining = false;
|
|
1486
2510
|
}
|
|
1487
2511
|
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Check if we should continue the flow and do so if needed
|
|
2514
|
+
*/
|
|
1488
2515
|
async checkForContinuation() {
|
|
1489
2516
|
if (this.continuationPending || this.isLoading) {
|
|
1490
|
-
this.continuationSkipped =
|
|
2517
|
+
this.continuationSkipped = true;
|
|
1491
2518
|
return;
|
|
1492
2519
|
}
|
|
1493
2520
|
if (this.shouldAutoSend()) {
|
|
1494
|
-
this.continuationPending =
|
|
1495
|
-
|
|
2521
|
+
this.continuationPending = true;
|
|
2522
|
+
this.continuationSkipped = false;
|
|
2523
|
+
let succeeded = false;
|
|
1496
2524
|
try {
|
|
1497
|
-
|
|
2525
|
+
succeeded = await this.streamResponse();
|
|
1498
2526
|
} finally {
|
|
1499
|
-
this.continuationPending =
|
|
2527
|
+
this.continuationPending = false;
|
|
2528
|
+
}
|
|
2529
|
+
if (this.continuationSkipped && succeeded) {
|
|
2530
|
+
this.continuationSkipped = false;
|
|
2531
|
+
await this.checkForContinuation();
|
|
1500
2532
|
}
|
|
1501
|
-
this.continuationSkipped && e && (this.continuationSkipped = !1, await this.checkForContinuation());
|
|
1502
2533
|
}
|
|
1503
2534
|
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Check if all tool calls are complete and we should auto-send.
|
|
2537
|
+
* Requires that there is at least one tool call in the last assistant message;
|
|
2538
|
+
* a text-only response has nothing to auto-send.
|
|
2539
|
+
*/
|
|
1504
2540
|
shouldAutoSend() {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
2541
|
+
const lastAssistant = this.processor.getMessages().findLast((m) => m.role === "assistant");
|
|
2542
|
+
if (!lastAssistant) return false;
|
|
2543
|
+
if (!lastAssistant.parts.some((p) => p.type === "tool-call")) return false;
|
|
2544
|
+
return this.processor.areAllToolsComplete();
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Get current messages
|
|
2548
|
+
*/
|
|
1508
2549
|
getMessages() {
|
|
1509
2550
|
return this.processor.getMessages();
|
|
1510
2551
|
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Get loading state
|
|
2554
|
+
*/
|
|
1511
2555
|
getIsLoading() {
|
|
1512
2556
|
return this.isLoading;
|
|
1513
2557
|
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Get current status
|
|
2560
|
+
*/
|
|
1514
2561
|
getStatus() {
|
|
1515
2562
|
return this.status;
|
|
1516
2563
|
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Get whether the subscription loop is active
|
|
2566
|
+
*/
|
|
1517
2567
|
getIsSubscribed() {
|
|
1518
2568
|
return this.isSubscribed;
|
|
1519
2569
|
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Get current connection lifecycle status
|
|
2572
|
+
*/
|
|
1520
2573
|
getConnectionStatus() {
|
|
1521
2574
|
return this.connectionStatus;
|
|
1522
2575
|
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Whether the shared session is actively generating.
|
|
2578
|
+
* Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).
|
|
2579
|
+
* Unlike `isLoading` (request-local), this reflects shared generation
|
|
2580
|
+
* activity visible to all subscribers (e.g. across tabs/devices).
|
|
2581
|
+
*/
|
|
1523
2582
|
getSessionGenerating() {
|
|
1524
2583
|
return this.sessionGenerating;
|
|
1525
2584
|
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Get current error
|
|
2587
|
+
*/
|
|
1526
2588
|
getError() {
|
|
1527
2589
|
return this.error;
|
|
1528
2590
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
2591
|
+
/**
|
|
2592
|
+
* Manually set messages
|
|
2593
|
+
*/
|
|
2594
|
+
setMessagesManually(messages) {
|
|
2595
|
+
this.processor.setMessages(messages);
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Update options refs (for use in React hooks to avoid recreating client)
|
|
2599
|
+
*/
|
|
2600
|
+
updateOptions(options) {
|
|
2601
|
+
if (options.connection !== void 0) {
|
|
2602
|
+
const wasSubscribed = this.isSubscribed;
|
|
2603
|
+
if (this.isLoading) this.cancelInFlightStream({
|
|
2604
|
+
setReadyStatus: true,
|
|
2605
|
+
abortSubscription: true
|
|
2606
|
+
});
|
|
2607
|
+
else if (wasSubscribed) this.abortSubscriptionLoop();
|
|
2608
|
+
this.resetSessionGenerating();
|
|
2609
|
+
this.setIsSubscribed(false);
|
|
2610
|
+
this.setConnectionStatus("disconnected");
|
|
2611
|
+
this.connection = normalizeConnectionAdapter(options.connection);
|
|
2612
|
+
if (wasSubscribed) this.subscribe();
|
|
1539
2613
|
}
|
|
1540
|
-
if (
|
|
2614
|
+
if (options.body !== void 0) this.body = options.body;
|
|
2615
|
+
if (options.tools !== void 0) {
|
|
1541
2616
|
this.clientToolsRef.current = /* @__PURE__ */ new Map();
|
|
1542
|
-
for (
|
|
2617
|
+
for (const tool of options.tools) this.clientToolsRef.current.set(tool.name, tool);
|
|
1543
2618
|
}
|
|
1544
|
-
|
|
2619
|
+
if (options.onResponse !== void 0) this.callbacksRef.current.onResponse = options.onResponse;
|
|
2620
|
+
if (options.onChunk !== void 0) this.callbacksRef.current.onChunk = options.onChunk;
|
|
2621
|
+
if (options.onFinish !== void 0) this.callbacksRef.current.onFinish = options.onFinish;
|
|
2622
|
+
if (options.onError !== void 0) this.callbacksRef.current.onError = options.onError;
|
|
2623
|
+
if (options.onSubscriptionChange !== void 0) this.callbacksRef.current.onSubscriptionChange = options.onSubscriptionChange;
|
|
2624
|
+
if (options.onConnectionStatusChange !== void 0) this.callbacksRef.current.onConnectionStatusChange = options.onConnectionStatusChange;
|
|
2625
|
+
if (options.onSessionGeneratingChange !== void 0) this.callbacksRef.current.onSessionGeneratingChange = options.onSessionGeneratingChange;
|
|
2626
|
+
if (options.onCustomEvent !== void 0) this.callbacksRef.current.onCustomEvent = options.onCustomEvent;
|
|
1545
2627
|
}
|
|
1546
2628
|
};
|
|
1547
2629
|
//#endregion
|
|
1548
2630
|
//#region ../../node_modules/.pnpm/@tanstack+ai-client@0.8.0/node_modules/@tanstack/ai-client/dist/esm/types.js
|
|
1549
|
-
function clientTools(...
|
|
1550
|
-
return
|
|
2631
|
+
function clientTools(...tools) {
|
|
2632
|
+
return tools;
|
|
1551
2633
|
}
|
|
1552
2634
|
//#endregion
|
|
1553
|
-
//#region ../../node_modules/.pnpm/@tanstack+ai-react@0.8.0_@tanstack+ai@0.14.0_@types+react@19.
|
|
1554
|
-
function useChat(
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
2635
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai-react@0.8.0_@tanstack+ai@0.14.0_@types+react@19.3.0_react@19.3.0/node_modules/@tanstack/ai-react/dist/esm/use-chat.js
|
|
2636
|
+
function useChat(options) {
|
|
2637
|
+
const hookId = useId();
|
|
2638
|
+
const clientId = options.id || hookId;
|
|
2639
|
+
const [messages, setMessages] = useState(options.initialMessages || []);
|
|
2640
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
2641
|
+
const [error, setError] = useState(void 0);
|
|
2642
|
+
const [status, setStatus] = useState("ready");
|
|
2643
|
+
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
2644
|
+
const [connectionStatus, setConnectionStatus] = useState("disconnected");
|
|
2645
|
+
const [sessionGenerating, setSessionGenerating] = useState(false);
|
|
2646
|
+
const messagesRef = useRef(options.initialMessages || []);
|
|
2647
|
+
const isFirstMountRef = useRef(true);
|
|
2648
|
+
messagesRef.current = messages;
|
|
2649
|
+
const optionsRef = useRef(options);
|
|
2650
|
+
optionsRef.current = options;
|
|
2651
|
+
const client = useMemo(() => {
|
|
2652
|
+
const messagesToUse = isFirstMountRef.current ? options.initialMessages || [] : messagesRef.current;
|
|
2653
|
+
isFirstMountRef.current = false;
|
|
2654
|
+
return new ChatClient({
|
|
2655
|
+
connection: optionsRef.current.connection,
|
|
2656
|
+
id: clientId,
|
|
2657
|
+
initialMessages: messagesToUse,
|
|
2658
|
+
body: optionsRef.current.body,
|
|
2659
|
+
onResponse: (response) => optionsRef.current.onResponse?.(response),
|
|
2660
|
+
onChunk: (chunk) => optionsRef.current.onChunk?.(chunk),
|
|
2661
|
+
onFinish: (message) => {
|
|
2662
|
+
optionsRef.current.onFinish?.(message);
|
|
1570
2663
|
},
|
|
1571
|
-
onError: (
|
|
1572
|
-
|
|
2664
|
+
onError: (error2) => {
|
|
2665
|
+
optionsRef.current.onError?.(error2);
|
|
1573
2666
|
},
|
|
1574
|
-
tools:
|
|
1575
|
-
onCustomEvent: (
|
|
1576
|
-
streamProcessor:
|
|
1577
|
-
onMessagesChange: (
|
|
1578
|
-
|
|
2667
|
+
tools: optionsRef.current.tools,
|
|
2668
|
+
onCustomEvent: (eventType, data, context) => optionsRef.current.onCustomEvent?.(eventType, data, context),
|
|
2669
|
+
streamProcessor: options.streamProcessor,
|
|
2670
|
+
onMessagesChange: (newMessages) => {
|
|
2671
|
+
setMessages(newMessages);
|
|
1579
2672
|
},
|
|
1580
|
-
onLoadingChange: (
|
|
1581
|
-
|
|
2673
|
+
onLoadingChange: (newIsLoading) => {
|
|
2674
|
+
setIsLoading(newIsLoading);
|
|
1582
2675
|
},
|
|
1583
|
-
onErrorChange: (
|
|
1584
|
-
|
|
2676
|
+
onErrorChange: (newError) => {
|
|
2677
|
+
setError(newError);
|
|
1585
2678
|
},
|
|
1586
|
-
onStatusChange: (
|
|
1587
|
-
|
|
2679
|
+
onStatusChange: (status2) => {
|
|
2680
|
+
setStatus(status2);
|
|
1588
2681
|
},
|
|
1589
|
-
onSubscriptionChange: (
|
|
1590
|
-
|
|
2682
|
+
onSubscriptionChange: (nextIsSubscribed) => {
|
|
2683
|
+
setIsSubscribed(nextIsSubscribed);
|
|
1591
2684
|
},
|
|
1592
|
-
onConnectionStatusChange: (
|
|
1593
|
-
|
|
2685
|
+
onConnectionStatusChange: (nextStatus) => {
|
|
2686
|
+
setConnectionStatus(nextStatus);
|
|
1594
2687
|
},
|
|
1595
|
-
onSessionGeneratingChange: (
|
|
1596
|
-
|
|
2688
|
+
onSessionGeneratingChange: (isGenerating) => {
|
|
2689
|
+
setSessionGenerating(isGenerating);
|
|
1597
2690
|
}
|
|
1598
2691
|
});
|
|
1599
|
-
}, [
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
}, [
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}, [
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
}, [
|
|
1618
|
-
|
|
1619
|
-
|
|
2692
|
+
}, [clientId]);
|
|
2693
|
+
useEffect(() => {
|
|
2694
|
+
client.updateOptions({ body: options.body });
|
|
2695
|
+
}, [client, options.body]);
|
|
2696
|
+
useEffect(() => {
|
|
2697
|
+
if (options.initialMessages && options.initialMessages.length > 0) {
|
|
2698
|
+
if (messages.length === 0) client.setMessagesManually(options.initialMessages);
|
|
2699
|
+
}
|
|
2700
|
+
}, []);
|
|
2701
|
+
useEffect(() => {
|
|
2702
|
+
if (options.live) client.subscribe();
|
|
2703
|
+
else client.unsubscribe();
|
|
2704
|
+
}, [client, options.live]);
|
|
2705
|
+
useEffect(() => {
|
|
2706
|
+
return () => {
|
|
2707
|
+
if (options.live) client.unsubscribe();
|
|
2708
|
+
else client.stop();
|
|
2709
|
+
};
|
|
2710
|
+
}, [client, options.live]);
|
|
2711
|
+
const sendMessage = useCallback(async (content) => {
|
|
2712
|
+
await client.sendMessage(content);
|
|
2713
|
+
}, [client]);
|
|
2714
|
+
const append = useCallback(async (message) => {
|
|
2715
|
+
await client.append(message);
|
|
2716
|
+
}, [client]);
|
|
2717
|
+
const reload = useCallback(async () => {
|
|
2718
|
+
await client.reload();
|
|
2719
|
+
}, [client]);
|
|
2720
|
+
const stop = useCallback(() => {
|
|
2721
|
+
client.stop();
|
|
2722
|
+
}, [client]);
|
|
2723
|
+
const clear = useCallback(() => {
|
|
2724
|
+
client.clear();
|
|
2725
|
+
}, [client]);
|
|
1620
2726
|
return {
|
|
1621
|
-
messages
|
|
1622
|
-
sendMessage
|
|
1623
|
-
append
|
|
1624
|
-
reload
|
|
1625
|
-
stop
|
|
1626
|
-
isLoading
|
|
1627
|
-
error
|
|
1628
|
-
status
|
|
1629
|
-
isSubscribed
|
|
1630
|
-
connectionStatus
|
|
1631
|
-
sessionGenerating
|
|
1632
|
-
setMessages:
|
|
1633
|
-
|
|
1634
|
-
}, [
|
|
1635
|
-
clear
|
|
1636
|
-
addToolResult:
|
|
1637
|
-
await
|
|
1638
|
-
}, [
|
|
1639
|
-
addToolApprovalResponse:
|
|
1640
|
-
await
|
|
1641
|
-
}, [
|
|
2727
|
+
messages,
|
|
2728
|
+
sendMessage,
|
|
2729
|
+
append,
|
|
2730
|
+
reload,
|
|
2731
|
+
stop,
|
|
2732
|
+
isLoading,
|
|
2733
|
+
error,
|
|
2734
|
+
status,
|
|
2735
|
+
isSubscribed,
|
|
2736
|
+
connectionStatus,
|
|
2737
|
+
sessionGenerating,
|
|
2738
|
+
setMessages: useCallback((newMessages) => {
|
|
2739
|
+
client.setMessagesManually(newMessages);
|
|
2740
|
+
}, [client]),
|
|
2741
|
+
clear,
|
|
2742
|
+
addToolResult: useCallback(async (result) => {
|
|
2743
|
+
await client.addToolResult(result);
|
|
2744
|
+
}, [client]),
|
|
2745
|
+
addToolApprovalResponse: useCallback(async (response) => {
|
|
2746
|
+
await client.addToolApprovalResponse(response);
|
|
2747
|
+
}, [client])
|
|
1642
2748
|
};
|
|
1643
2749
|
}
|
|
1644
|
-
(/* @__PURE__ */ new Uint8Array(128)).fill(128)
|
|
2750
|
+
(/* @__PURE__ */ new Uint8Array(128)).fill(128);
|
|
2751
|
+
createContext(null);
|
|
1645
2752
|
//#endregion
|
|
1646
|
-
//#region ../../node_modules/.pnpm/@tanstack+ai-react-ui@0.6.2_@tanstack+ai-client@0.8.0_@tanstack+ai-react@0.8.0_@
|
|
1647
|
-
function ThinkingPart({ content
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
}, [
|
|
1652
|
-
|
|
2753
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai-react-ui@0.6.2_@tanstack+ai-client@0.8.0_@tanstack+ai-react@0.8.0_@tanstac_f31ad401e8e50c178d70c0e33691bf47/node_modules/@tanstack/ai-react-ui/dist/esm/thinking-part.js
|
|
2754
|
+
function ThinkingPart({ content, className = "", isComplete = false }) {
|
|
2755
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
2756
|
+
useEffect(() => {
|
|
2757
|
+
if (isComplete) setIsCollapsed(true);
|
|
2758
|
+
}, [isComplete]);
|
|
2759
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2760
|
+
className: className || void 0,
|
|
1653
2761
|
"data-part-type": "thinking",
|
|
1654
|
-
"data-part-content":
|
|
1655
|
-
children: [/* @__PURE__ */
|
|
1656
|
-
onClick: () =>
|
|
2762
|
+
"data-part-content": true,
|
|
2763
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
2764
|
+
onClick: () => setIsCollapsed(!isCollapsed),
|
|
1657
2765
|
className: "flex items-center gap-2 text-sm text-gray-400 hover:text-gray-300 transition-colors mb-2",
|
|
1658
|
-
"aria-expanded": !
|
|
1659
|
-
"aria-label":
|
|
2766
|
+
"aria-expanded": !isCollapsed,
|
|
2767
|
+
"aria-label": isCollapsed ? "Expand thinking" : "Collapse thinking",
|
|
1660
2768
|
children: [
|
|
1661
|
-
/* @__PURE__ */
|
|
2769
|
+
/* @__PURE__ */ jsx("span", {
|
|
1662
2770
|
className: "text-xs",
|
|
1663
|
-
children:
|
|
2771
|
+
children: isCollapsed ? "▶" : "▼"
|
|
1664
2772
|
}),
|
|
1665
|
-
/* @__PURE__ */
|
|
2773
|
+
/* @__PURE__ */ jsx("span", {
|
|
1666
2774
|
className: "italic",
|
|
1667
2775
|
children: "💭 Thinking..."
|
|
1668
2776
|
}),
|
|
1669
|
-
|
|
2777
|
+
isComplete && /* @__PURE__ */ jsx("span", {
|
|
1670
2778
|
className: "text-xs text-gray-500",
|
|
1671
2779
|
children: "(complete)"
|
|
1672
2780
|
})
|
|
1673
2781
|
]
|
|
1674
|
-
}), !
|
|
2782
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
1675
2783
|
className: "text-gray-300 whitespace-pre-wrap font-mono text-sm",
|
|
1676
|
-
children:
|
|
2784
|
+
children: content
|
|
1677
2785
|
})]
|
|
1678
2786
|
});
|
|
1679
2787
|
}
|
|
1680
2788
|
//#endregion
|
|
1681
|
-
//#region ../../node_modules/.pnpm/@tanstack+ai-react-ui@0.6.2_@tanstack+ai-client@0.8.0_@tanstack+ai-react@0.8.0_@
|
|
1682
|
-
function ChatMessage({ message
|
|
1683
|
-
|
|
1684
|
-
return /* @__PURE__ */
|
|
1685
|
-
className:
|
|
1686
|
-
"data-message-id":
|
|
1687
|
-
"data-message-role":
|
|
1688
|
-
"data-message-created":
|
|
1689
|
-
children:
|
|
1690
|
-
|
|
1691
|
-
return /* @__PURE__ */
|
|
1692
|
-
part
|
|
1693
|
-
isThinkingComplete
|
|
1694
|
-
textPartRenderer
|
|
1695
|
-
thinkingPartRenderer
|
|
1696
|
-
toolsRenderer
|
|
1697
|
-
defaultToolRenderer
|
|
1698
|
-
toolResultRenderer
|
|
1699
|
-
}, `${
|
|
2789
|
+
//#region ../../node_modules/.pnpm/@tanstack+ai-react-ui@0.6.2_@tanstack+ai-client@0.8.0_@tanstack+ai-react@0.8.0_@tanstac_f31ad401e8e50c178d70c0e33691bf47/node_modules/@tanstack/ai-react-ui/dist/esm/chat-message.js
|
|
2790
|
+
function ChatMessage({ message, className = "", userClassName = "", assistantClassName = "", textPartRenderer, thinkingPartRenderer, toolsRenderer, defaultToolRenderer, toolResultRenderer }) {
|
|
2791
|
+
const combinedClassName = [className, message.role === "user" ? userClassName : assistantClassName].filter(Boolean).join(" ");
|
|
2792
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2793
|
+
className: combinedClassName || void 0,
|
|
2794
|
+
"data-message-id": message.id,
|
|
2795
|
+
"data-message-role": message.role,
|
|
2796
|
+
"data-message-created": message.createdAt?.toISOString(),
|
|
2797
|
+
children: message.parts.map((part, index) => {
|
|
2798
|
+
const isThinkingComplete = part.type === "thinking" && message.parts.slice(index + 1).some((p) => p.type === "text");
|
|
2799
|
+
return /* @__PURE__ */ jsx(MessagePart, {
|
|
2800
|
+
part,
|
|
2801
|
+
isThinkingComplete,
|
|
2802
|
+
textPartRenderer,
|
|
2803
|
+
thinkingPartRenderer,
|
|
2804
|
+
toolsRenderer,
|
|
2805
|
+
defaultToolRenderer,
|
|
2806
|
+
toolResultRenderer
|
|
2807
|
+
}, `${message.id}-part-${index}`);
|
|
1700
2808
|
})
|
|
1701
2809
|
});
|
|
1702
2810
|
}
|
|
1703
|
-
function MessagePart({ part
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1706
|
-
"
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
2811
|
+
function MessagePart({ part, isThinkingComplete, textPartRenderer, thinkingPartRenderer, toolsRenderer, defaultToolRenderer, toolResultRenderer }) {
|
|
2812
|
+
if (part.type === "text") {
|
|
2813
|
+
if (textPartRenderer) return /* @__PURE__ */ jsx(Fragment$1, { children: textPartRenderer({ content: part.content }) });
|
|
2814
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2815
|
+
"data-part-type": "text",
|
|
2816
|
+
"data-part-content": true,
|
|
2817
|
+
children: part.content
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
if (part.type === "thinking") {
|
|
2821
|
+
if (thinkingPartRenderer) return /* @__PURE__ */ jsx(Fragment$1, { children: thinkingPartRenderer({
|
|
2822
|
+
content: part.content,
|
|
2823
|
+
isComplete: isThinkingComplete
|
|
2824
|
+
}) });
|
|
2825
|
+
return /* @__PURE__ */ jsx(ThinkingPart, {
|
|
2826
|
+
content: part.content,
|
|
2827
|
+
isComplete: isThinkingComplete
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
if (part.type === "tool-call") {
|
|
2831
|
+
const toolProps = {
|
|
2832
|
+
id: part.id,
|
|
2833
|
+
name: part.name,
|
|
2834
|
+
arguments: part.arguments,
|
|
2835
|
+
state: part.state,
|
|
2836
|
+
approval: part.approval,
|
|
2837
|
+
output: part.output
|
|
1724
2838
|
};
|
|
1725
|
-
|
|
2839
|
+
if (toolsRenderer?.[part.name]) return /* @__PURE__ */ jsx(Fragment$1, { children: toolsRenderer[part.name]?.(toolProps) });
|
|
2840
|
+
if (defaultToolRenderer) return /* @__PURE__ */ jsx(Fragment$1, { children: defaultToolRenderer(toolProps) });
|
|
2841
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1726
2842
|
"data-part-type": "tool-call",
|
|
1727
|
-
"data-tool-name":
|
|
1728
|
-
"data-tool-state":
|
|
1729
|
-
"data-tool-id":
|
|
2843
|
+
"data-tool-name": part.name,
|
|
2844
|
+
"data-tool-state": part.state,
|
|
2845
|
+
"data-tool-id": part.id,
|
|
1730
2846
|
children: [
|
|
1731
|
-
/* @__PURE__ */
|
|
1732
|
-
"data-tool-header":
|
|
1733
|
-
children: [/* @__PURE__ */
|
|
1734
|
-
"data-tool-state-badge":
|
|
1735
|
-
children:
|
|
2847
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2848
|
+
"data-tool-header": true,
|
|
2849
|
+
children: [/* @__PURE__ */ jsx("strong", { children: part.name }), /* @__PURE__ */ jsx("span", {
|
|
2850
|
+
"data-tool-state-badge": true,
|
|
2851
|
+
children: part.state
|
|
1736
2852
|
})]
|
|
1737
2853
|
}),
|
|
1738
|
-
|
|
1739
|
-
"data-tool-arguments":
|
|
1740
|
-
children: /* @__PURE__ */
|
|
2854
|
+
part.arguments && /* @__PURE__ */ jsx("div", {
|
|
2855
|
+
"data-tool-arguments": true,
|
|
2856
|
+
children: /* @__PURE__ */ jsx("pre", { children: part.arguments })
|
|
1741
2857
|
}),
|
|
1742
|
-
|
|
1743
|
-
"data-tool-approval":
|
|
1744
|
-
children:
|
|
2858
|
+
part.approval && /* @__PURE__ */ jsx("div", {
|
|
2859
|
+
"data-tool-approval": true,
|
|
2860
|
+
children: part.approval.approved !== void 0 ? part.approval.approved ? "✓ Approved" : "✗ Denied" : "⏳ Awaiting approval..."
|
|
1745
2861
|
}),
|
|
1746
|
-
|
|
1747
|
-
"data-tool-output":
|
|
1748
|
-
children: /* @__PURE__ */
|
|
2862
|
+
part.output && /* @__PURE__ */ jsx("div", {
|
|
2863
|
+
"data-tool-output": true,
|
|
2864
|
+
children: /* @__PURE__ */ jsx("pre", { children: JSON.stringify(part.output, null, 2) })
|
|
1749
2865
|
})
|
|
1750
2866
|
]
|
|
1751
2867
|
});
|
|
1752
2868
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
"
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
"data-tool-result-
|
|
1763
|
-
children:
|
|
1764
|
-
|
|
1765
|
-
|
|
2869
|
+
if (part.type === "tool-result") {
|
|
2870
|
+
if (toolResultRenderer) return /* @__PURE__ */ jsx(Fragment$1, { children: toolResultRenderer({
|
|
2871
|
+
toolCallId: part.toolCallId,
|
|
2872
|
+
content: part.content,
|
|
2873
|
+
state: part.state
|
|
2874
|
+
}) });
|
|
2875
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2876
|
+
"data-part-type": "tool-result",
|
|
2877
|
+
"data-tool-call-id": part.toolCallId,
|
|
2878
|
+
"data-tool-result-state": part.state,
|
|
2879
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2880
|
+
"data-tool-result-content": true,
|
|
2881
|
+
children: part.content
|
|
2882
|
+
})
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
return null;
|
|
1766
2886
|
}
|
|
1767
2887
|
//#endregion
|
|
1768
2888
|
export { toolDefinition as a, fetchServerSentEvents as i, useChat as n, clientTools as r, ChatMessage as t };
|