@aistrike-dev/ui-shell-dev 1.0.1 → 1.0.3

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.
@@ -0,0 +1,246 @@
1
+ //#region src/mock/responder.ts
2
+ var counter = 0;
3
+ var nextId = () => {
4
+ counter += 1;
5
+ return `mock-${Date.now().toString(36)}-${counter}`;
6
+ };
7
+ var words = (text) => text.split(/(\s+)/).filter((word) => word !== "");
8
+ /** Text as a handful of deltas, the way a model streams it. */
9
+ function say(text) {
10
+ const parts = words(text);
11
+ const out = [];
12
+ for (let i = 0; i < parts.length; i += 6) out.push({
13
+ type: "text",
14
+ delta: parts.slice(i, i + 6).join("")
15
+ });
16
+ return [...out, {
17
+ type: "done",
18
+ stop: "end"
19
+ }];
20
+ }
21
+ var call = (tool, input) => {
22
+ return {
23
+ type: "tool_use",
24
+ id: nextId(),
25
+ tool,
26
+ input
27
+ };
28
+ };
29
+ var humanize = (qualified) => {
30
+ const spaced = (qualified.split("__")[1] ?? qualified).replace(/_/g, " ");
31
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
32
+ };
33
+ function lastUser(messages) {
34
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
35
+ const message = messages[i];
36
+ if (message.role === "user") return {
37
+ message,
38
+ index: i
39
+ };
40
+ }
41
+ return null;
42
+ }
43
+ var VERBS = /* @__PURE__ */ new Set([
44
+ "select",
45
+ "show",
46
+ "highlight",
47
+ "these",
48
+ "those"
49
+ ]);
50
+ /** Entities on screen that the request describes by a word in their label or summary; all of them if none match. */
51
+ function described(text, screen) {
52
+ const terms = text.toLowerCase().split(/\W+/).filter((word) => word.length > 3 && !VERBS.has(word));
53
+ const hits = screen.entities.filter((ref) => terms.some((term) => `${ref.label} ${ref.summary ?? ""}`.toLowerCase().includes(term)));
54
+ return hits.length > 0 ? hits : screen.entities;
55
+ }
56
+ function describeScreen(user, tools) {
57
+ const can = tools.length === 0 ? "nothing yet" : tools.map((tool) => humanize(tool.name)).join(", ");
58
+ if (user.screen === void 0) return `Nothing on screen is shared with me right now. Reference something with @, or ask me to use one of: ${can}.`;
59
+ const { title, summary, entities } = user.screen;
60
+ const lead = `You're looking at ${title}${summary === void 0 ? "" : ` (${summary})`}, with ${entities.length} item(s) in view.`;
61
+ const top = entities.slice(0, 3).map((ref) => ref.label).join("; ");
62
+ return `${lead}${top === "" ? "" : ` First up: ${top}.`}\n\nHere I can: ${can}.`;
63
+ }
64
+ var toolRound = (calls, acting, reason) => {
65
+ return {
66
+ events: [...calls, {
67
+ type: "done",
68
+ stop: "tool_use"
69
+ }],
70
+ acting,
71
+ reason
72
+ };
73
+ };
74
+ /** Entities on screen the text names by a word in their label or summary; none if it names none. */
75
+ function named(text, screen) {
76
+ if (screen === void 0) return [];
77
+ const all = described(text, screen);
78
+ return all.length === screen.entities.length && all.length > 1 ? [] : all;
79
+ }
80
+ /** "Suppress a4", "suppress the cluster": the write tool once for each alert meant. */
81
+ function suppressing(user, names) {
82
+ const tool = names.find((name) => name.includes("__suppress_"));
83
+ if (!/suppress/i.test(user.text) || tool === void 0) return null;
84
+ const id = /\b([a-z]\d+)\b/i.exec(user.text)?.[1];
85
+ const fromText = id === void 0 ? named(user.text, user.screen).map((r) => r.id) : [id];
86
+ const ids = user.refs.length > 0 ? user.refs.map((ref) => ref.id) : fromText;
87
+ if (ids.length === 0) return null;
88
+ return ids.length === 1 ? toolRound([call(tool, { id: ids[0] })], "agent", "one change, nothing to assume") : toolRound(ids.map((one) => call(tool, { id: one })), "plan", "more than one change, so a plan first");
89
+ }
90
+ /** "Select the cluster": the page's select tool on the referenced entities, or the ones on screen the text describes. */
91
+ function selecting(user, names) {
92
+ const tool = names.find((name) => name.includes("__select_"));
93
+ if (!/\b(select|show me|highlight)\b/i.test(user.text) || tool === void 0) return null;
94
+ const onScreen = user.screen === void 0 ? [] : described(user.text, user.screen);
95
+ return toolRound([call(tool, { ids: (user.refs.length > 0 ? user.refs : onScreen).map((ref) => ref.id) })], "agent", "it only changes what you see");
96
+ }
97
+ /** A reference: each one looked up with its domain's `get_<type>` tool. */
98
+ function lookingUp(user, names) {
99
+ const lookups = user.refs.flatMap((ref) => {
100
+ const tool = `${ref.domain}__get_${ref.type.replace(/-/g, "_")}`;
101
+ return names.includes(tool) ? [call(tool, { id: ref.id })] : [];
102
+ });
103
+ return lookups.length > 0 ? toolRound(lookups, "ask", "looking it up changes nothing") : null;
104
+ }
105
+ /** The inputs for one expected call: as declared, else built from the refs or the screen (suggestions spec 7). */
106
+ function inputsFor(expected, spec, user) {
107
+ if (expected.input !== void 0) return [expected.input];
108
+ const properties = spec.input?.properties ?? {};
109
+ if ("ids" in properties) {
110
+ const onScreen = user.screen === void 0 ? [] : described(user.text, user.screen);
111
+ return [{ ids: (user.refs.length > 0 ? user.refs : onScreen).map((ref) => ref.id) }];
112
+ }
113
+ if ("id" in properties) return user.refs.map((ref) => {
114
+ return { id: ref.id };
115
+ });
116
+ return [{}];
117
+ }
118
+ /** A domain's suggestion: the calls it expects, made as it says, so a demo needs no script (suggestions spec 7). */
119
+ function following(user, tools) {
120
+ const from = user.suggestion;
121
+ if (from?.expects === void 0) return null;
122
+ const calls = from.expects.flatMap((expected) => {
123
+ const spec = tools.find((tool) => tool.name === `${from.domainId}__${expected.tool}`);
124
+ return spec === void 0 ? [] : inputsFor(expected, spec, user).map((input) => {
125
+ return {
126
+ spec,
127
+ input
128
+ };
129
+ });
130
+ });
131
+ if (calls.length === 0) return null;
132
+ const changes = calls.filter(({ spec }) => spec.effect !== "read").length;
133
+ const events = calls.map(({ spec, input }) => call(spec.name, input));
134
+ if (changes === 0) return toolRound(events, "ask", "following the suggestion changes nothing");
135
+ return changes === 1 ? toolRound(events, "agent", "one change, nothing to assume") : toolRound(events, "plan", "more than one change, so a plan first");
136
+ }
137
+ var CHANGE = /\b(suppress|select|highlight|close|assign|delete|disable)\b/i;
138
+ var ASK_REFUSAL = "Ask mode changes nothing, so I left it as it is. Switch to Auto, Plan or Agent and send it again.";
139
+ function firstRound(user, tools, mode) {
140
+ const suggested = following(user, tools);
141
+ if (suggested === null && mode === "ask" && CHANGE.test(user.text)) return say(ASK_REFUSAL);
142
+ const names = tools.map((tool) => tool.name);
143
+ const answer = suggested ?? suppressing(user, names) ?? selecting(user, names) ?? lookingUp(user, names) ?? {
144
+ events: say(describeScreen(user, tools)),
145
+ acting: "ask",
146
+ reason: "a question, so nothing changes"
147
+ };
148
+ return mode === "auto" ? [{
149
+ type: "mode",
150
+ acting: answer.acting,
151
+ reason: answer.reason
152
+ }, ...answer.events] : answer.events;
153
+ }
154
+ /** A tool result as a sentence. */
155
+ function sentence(tool, ok, result, error) {
156
+ if (!ok) return `${humanize(tool)} failed: ${error ?? "unknown error"}.`;
157
+ const value = result ?? {};
158
+ if (value.declined === true) return "Understood, I left it alone.";
159
+ if (tool.includes("__select_")) return `I selected ${typeof value.selected === "number" ? value.selected : 0} on the page.`;
160
+ if (tool.includes("__suppress_")) return `Done: ${typeof value.id === "string" ? value.id : "it"} is suppressed.`;
161
+ const facts = Object.entries(value).filter(([, v]) => typeof v === "string" || typeof v === "number" || typeof v === "boolean").slice(0, 7).map(([k, v]) => `${k} ${String(v)}`);
162
+ return `${typeof value.title === "string" ? value.title : humanize(tool)}: ${facts.join(", ")}.`;
163
+ }
164
+ /** Which tool each call id named, from the assistant messages (as stored, or as the API takes them). */
165
+ function callsIn(messages) {
166
+ const calls = /* @__PURE__ */ new Map();
167
+ for (const message of messages) {
168
+ const blocks = message.role === "assistant" ? message.blocks : [];
169
+ for (const block of blocks.filter((b) => b.kind === "tool")) {
170
+ const { id, tool } = block.call ?? {
171
+ id: block.id,
172
+ tool: block.tool
173
+ };
174
+ if (id !== void 0 && tool !== void 0) calls.set(id, tool);
175
+ }
176
+ }
177
+ return calls;
178
+ }
179
+ function afterTools(messages, from) {
180
+ const recent = messages.slice(from);
181
+ const calls = callsIn(recent);
182
+ return say(recent.flatMap((message) => message.role === "tool" ? message.results.map((result) => sentence(calls.get(result.id) ?? "tool", result.ok, result.result, result.error)) : []).join("\n\n"));
183
+ }
184
+ function respond(request) {
185
+ const found = lastUser(request.messages);
186
+ if (found === null) return say("Ask me something.");
187
+ return request.messages.slice(found.index + 1).some((message) => message.role === "tool") ? afterTools(request.messages, found.index + 1) : firstRound(found.message, request.tools, request.mode ?? "auto");
188
+ }
189
+ //#endregion
190
+ //#region src/mock/chatHandler.ts
191
+ async function readBody(req) {
192
+ const chunks = [];
193
+ for await (const chunk of req) chunks.push(chunk);
194
+ try {
195
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
196
+ } catch {
197
+ return;
198
+ }
199
+ }
200
+ var isRequest = (value) => typeof value === "object" && value !== null && Array.isArray(value.messages) && Array.isArray(value.tools);
201
+ var pause = async (ms) => new Promise((resolve) => {
202
+ setTimeout(resolve, ms);
203
+ });
204
+ /** Dev-only `POST /api/chat`: the scripted responder, streamed as server-sent events with a little pacing. */
205
+ function createChatHandler(options = {}) {
206
+ const delayMs = options.delayMs ?? 40;
207
+ return (req, res) => {
208
+ if ((req.url ?? "").split("?")[0] !== "/api/chat") return false;
209
+ if (req.method !== "POST") {
210
+ res.statusCode = 405;
211
+ res.end();
212
+ return true;
213
+ }
214
+ readBody(req).then(async (body) => {
215
+ if (!isRequest(body)) {
216
+ res.statusCode = 400;
217
+ res.end();
218
+ return;
219
+ }
220
+ res.statusCode = 200;
221
+ res.setHeader("Content-Type", "text/event-stream");
222
+ res.setHeader("Cache-Control", "no-cache");
223
+ for (const event of respond(body)) {
224
+ const { type, ...data } = event;
225
+ res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
226
+ await pause(delayMs);
227
+ }
228
+ res.end();
229
+ }).catch(() => {
230
+ res.statusCode = 500;
231
+ res.end();
232
+ });
233
+ return true;
234
+ };
235
+ }
236
+ //#endregion
237
+ //#region src/mock/index.ts
238
+ /**
239
+ * The assistant's dev API, fresh state per call. Its own dev server and the dev host's bundled copy
240
+ * (`@aistrike-dev/ui-shell-dev`, which loads the build of this file) both run it. Never part of the MFE's build.
241
+ */
242
+ function createMockHandlers() {
243
+ return [createChatHandler()];
244
+ }
245
+ //#endregion
246
+ export { createMockHandlers };