@case-framework/survey-assistant 0.6.0 → 0.7.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.
Files changed (94) hide show
  1. package/README.md +9 -4
  2. package/dist/{authoring-references-CW38pYzK.mjs → authoring-references-BJQ_1nMX.mjs} +2 -2
  3. package/dist/authoring-references-BJQ_1nMX.mjs.map +1 -0
  4. package/dist/capabilities-1KNnNO1_.mjs +73 -0
  5. package/dist/capabilities-1KNnNO1_.mjs.map +1 -0
  6. package/dist/capabilities-default.d.mts +1 -1
  7. package/dist/capabilities-default.d.mts.map +1 -1
  8. package/dist/capabilities-default.mjs +22 -25
  9. package/dist/capabilities-default.mjs.map +1 -1
  10. package/dist/{constants-BMGTgfjv.d.mts → constants-Be91Gay9.d.mts} +3 -3
  11. package/dist/{constants-BMGTgfjv.d.mts.map → constants-Be91Gay9.d.mts.map} +1 -1
  12. package/dist/{controller-proxy-DMTqer6T.mjs → controller-proxy-BH05e-C2.mjs} +21 -8
  13. package/dist/controller-proxy-BH05e-C2.mjs.map +1 -0
  14. package/dist/{controller-proxy-mRvez2qw.d.mts → controller-proxy-Bd0ZHBPF.d.mts} +11 -11
  15. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +1 -0
  16. package/dist/{engine-CQwOAcwK.mjs → engine-Bo85Oyj0.mjs} +2985 -2347
  17. package/dist/engine-Bo85Oyj0.mjs.map +1 -0
  18. package/dist/engine.d.mts +2 -2
  19. package/dist/engine.mjs +3 -3
  20. package/dist/{index-D6nqkz0O.d.mts → index-Tnqv-yKW.d.mts} +56 -71
  21. package/dist/index-Tnqv-yKW.d.mts.map +1 -0
  22. package/dist/index-l_EK4yK7.d.mts +404 -0
  23. package/dist/index-l_EK4yK7.d.mts.map +1 -0
  24. package/dist/{index-DvTqML-0.d.mts → index-nhrgbgO5.d.mts} +19 -5
  25. package/dist/index-nhrgbgO5.d.mts.map +1 -0
  26. package/dist/{index-B0lxsAzx.d.mts → index-x0qprHgK.d.mts} +19 -7
  27. package/dist/index-x0qprHgK.d.mts.map +1 -0
  28. package/dist/{lifecycle-BmGAIUlv.d.mts → lifecycle-Bx0aq_4S.d.mts} +7 -1
  29. package/dist/{lifecycle-BmGAIUlv.d.mts.map → lifecycle-Bx0aq_4S.d.mts.map} +1 -1
  30. package/dist/{memory-thread-repository-CjFGVNZh.mjs → memory-thread-repository-KWHQWFEW.mjs} +43 -12
  31. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +1 -0
  32. package/dist/{protocol-DJUP4gc0.mjs → protocol-BhzQbx81.mjs} +66 -16
  33. package/dist/protocol-BhzQbx81.mjs.map +1 -0
  34. package/dist/protocol.d.mts +3 -3
  35. package/dist/protocol.mjs +2 -2
  36. package/dist/react-B3ybjJ-X.mjs +1620 -0
  37. package/dist/react-B3ybjJ-X.mjs.map +1 -0
  38. package/dist/react-integration.d.mts +1 -1
  39. package/dist/react-integration.mjs +1 -1
  40. package/dist/react.d.mts +3 -3
  41. package/dist/react.mjs +3 -3
  42. package/dist/references/assistant-operations.md +45 -39
  43. package/dist/references/expressions.md +28 -0
  44. package/dist/references/item-types.md +1 -1
  45. package/dist/references/localization.md +5 -4
  46. package/dist/references/rich-text-content.md +60 -27
  47. package/dist/references/source-material-surveys.md +9 -2
  48. package/dist/references/survey-data-model.md +9 -1
  49. package/dist/{request-context-BjTHNcDd.mjs → request-context-CG4SWijt.mjs} +3 -3
  50. package/dist/{request-context-BjTHNcDd.mjs.map → request-context-CG4SWijt.mjs.map} +1 -1
  51. package/dist/server-agent.d.mts +19 -8
  52. package/dist/server-agent.d.mts.map +1 -1
  53. package/dist/server-agent.mjs +7 -11
  54. package/dist/server-agent.mjs.map +1 -1
  55. package/dist/server-runtime.d.mts +19 -13
  56. package/dist/server-runtime.d.mts.map +1 -1
  57. package/dist/server-runtime.mjs +7 -2
  58. package/dist/server-runtime.mjs.map +1 -1
  59. package/dist/server-tasks.d.mts +1 -1
  60. package/dist/server-tasks.mjs +2 -2
  61. package/dist/server-tools.d.mts +1 -1
  62. package/dist/server-tools.mjs +1 -1
  63. package/dist/server.d.mts +5 -5
  64. package/dist/server.mjs +3 -3
  65. package/dist/{tasks-DxzD25Zc.d.mts → tasks-CYqrcaRO.d.mts} +4 -4
  66. package/dist/{tasks-DxzD25Zc.d.mts.map → tasks-CYqrcaRO.d.mts.map} +1 -1
  67. package/dist/{thread-handlers-AbQbt4sX.d.mts → thread-handlers-DBAaU7BA.d.mts} +6 -4
  68. package/dist/thread-handlers-DBAaU7BA.d.mts.map +1 -0
  69. package/dist/{tools-CFh35R4d.mjs → tools-BvSovehT.mjs} +431 -495
  70. package/dist/tools-BvSovehT.mjs.map +1 -0
  71. package/dist/ui.css +1 -1
  72. package/dist/ui.d.mts +5 -4
  73. package/dist/ui.d.mts.map +1 -1
  74. package/dist/ui.mjs +619 -66
  75. package/dist/ui.mjs.map +1 -1
  76. package/docs/integration.md +40 -0
  77. package/package.json +21 -19
  78. package/dist/authoring-references-CW38pYzK.mjs.map +0 -1
  79. package/dist/controller-proxy-DMTqer6T.mjs.map +0 -1
  80. package/dist/controller-proxy-mRvez2qw.d.mts.map +0 -1
  81. package/dist/engine-CQwOAcwK.mjs.map +0 -1
  82. package/dist/form-authoring-kQDgC5oz.mjs +0 -155
  83. package/dist/form-authoring-kQDgC5oz.mjs.map +0 -1
  84. package/dist/index-B0lxsAzx.d.mts.map +0 -1
  85. package/dist/index-BS-Xjk1h.d.mts +0 -224
  86. package/dist/index-BS-Xjk1h.d.mts.map +0 -1
  87. package/dist/index-D6nqkz0O.d.mts.map +0 -1
  88. package/dist/index-DvTqML-0.d.mts.map +0 -1
  89. package/dist/memory-thread-repository-CjFGVNZh.mjs.map +0 -1
  90. package/dist/protocol-DJUP4gc0.mjs.map +0 -1
  91. package/dist/react-Dqlmasrs.mjs +0 -768
  92. package/dist/react-Dqlmasrs.mjs.map +0 -1
  93. package/dist/thread-handlers-AbQbt4sX.d.mts.map +0 -1
  94. package/dist/tools-CFh35R4d.mjs.map +0 -1
@@ -1,768 +0,0 @@
1
- import { H as surveyDocumentVersionSchema, M as surveyAssistantThreadSchema, N as surveyAssistantThreadTranscriptSchema, f as surveyChangeSetSchema, j as surveyAssistantThreadListSchema, l as surveyAssistantTurnFinalSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE, o as surveyAssistantApplyOutcomeSchema, p as surveyAssistantModelCatalogSchema, r as applyReceiptSchema } from "./protocol-DJUP4gc0.mjs";
2
- import { a as applySurveyAssistantLaunchUIContext, l as readSurveyAssistantJson } from "./controller-proxy-DMTqer6T.mjs";
3
- import { DefaultChatTransport } from "ai";
4
- import { useCallback, useEffect, useEffectEvent, useMemo, useReducer, useRef, useState } from "react";
5
- import { useChat } from "@ai-sdk/react";
6
- //#region src/react/session-state.ts
7
- const isSurveyAssistantTurnActive = ({ isStreaming, state }) => state !== "aborted" && (isStreaming || state === "submitting" || state === "streaming" || state === "applying");
8
- const reduceSurveyAssistantSessionState = (_state, event) => {
9
- switch (event.type) {
10
- case "submit": return "submitting";
11
- case "stream-started": return "streaming";
12
- case "completed": return "completed";
13
- case "apply-started": return "applying";
14
- case "applied": return "applied";
15
- case "failed": return "failed";
16
- case "aborted": return "aborted";
17
- case "reset": return event.hasTranscript ? "completed" : "idle";
18
- }
19
- };
20
- //#endregion
21
- //#region src/react/turn-authorization.ts
22
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23
- const isCompletedToolPart = (part) => (part.type === "dynamic-tool" || part.type.startsWith("tool-")) && "state" in part && part.state === "output-available" && "output" in part;
24
- const unwrapSurveyAssistantToolOutput = (output) => {
25
- if (isRecord(output) && "output" in output && output.output !== void 0) return output.output;
26
- return output;
27
- };
28
- const parseSurveyChangeSet = (output) => {
29
- const parsed = surveyChangeSetSchema.safeParse(unwrapSurveyAssistantToolOutput(output));
30
- return parsed.success ? parsed.data : null;
31
- };
32
- const collectSurveyChangeSets = (messages) => {
33
- const changes = [];
34
- for (const message of messages) for (const part of message.parts) {
35
- if (!isCompletedToolPart(part)) continue;
36
- const change = parseSurveyChangeSet(part.output);
37
- if (change) changes.push(change);
38
- }
39
- return changes;
40
- };
41
- const findLastUserMessageIndex$1 = (messages) => {
42
- for (let index = messages.length - 1; index >= 0; index -= 1) if (messages[index]?.role === "user") return index;
43
- return -1;
44
- };
45
- const resolveLatestSurveyTurnAuthorization = (messages) => {
46
- const lastUserIndex = findLastUserMessageIndex$1(messages);
47
- if (lastUserIndex < 0) return { status: "pending" };
48
- const turnMessages = messages.slice(lastUserIndex + 1);
49
- const finalParts = turnMessages.flatMap((message) => message.parts).filter((part) => part.type === SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE);
50
- if (finalParts.length === 0) return { status: "pending" };
51
- if (finalParts.length !== 1) return {
52
- status: "invalid",
53
- reason: "The assistant returned multiple turn finals."
54
- };
55
- const finalPart = finalParts[0];
56
- const finalParse = surveyAssistantTurnFinalSchema.safeParse(finalPart && "data" in finalPart ? finalPart.data : void 0);
57
- if (!finalParse.success) return {
58
- status: "invalid",
59
- reason: "The assistant returned an invalid turn final."
60
- };
61
- const turnFinal = finalParse.data;
62
- if (turnFinal.status !== "completed") return {
63
- status: turnFinal.status,
64
- turnFinal,
65
- changeSets: []
66
- };
67
- const candidates = collectSurveyChangeSets(turnMessages).filter((change) => change.turnId === turnFinal.turnId);
68
- const candidatesById = /* @__PURE__ */ new Map();
69
- for (const candidate of candidates) {
70
- if (candidatesById.has(candidate.id)) return {
71
- status: "invalid",
72
- turnId: turnFinal.turnId,
73
- reason: `The assistant returned duplicate change set "${candidate.id}".`
74
- };
75
- candidatesById.set(candidate.id, candidate);
76
- }
77
- for (const blockedChangeSetId of turnFinal.blockedChangeSetIds ?? []) {
78
- const candidate = candidatesById.get(blockedChangeSetId);
79
- if (!candidate || candidate.status !== "rejected" || candidate.validation.valid) return {
80
- status: "invalid",
81
- turnId: turnFinal.turnId,
82
- reason: `The assistant did not provide blocked change set "${blockedChangeSetId}".`
83
- };
84
- }
85
- const changeSets = [];
86
- for (const changeSetId of turnFinal.executableChangeSetIds) {
87
- const candidate = candidatesById.get(changeSetId);
88
- if (!candidate || candidate.status !== "validated" || !candidate.validation.valid) return {
89
- status: "invalid",
90
- turnId: turnFinal.turnId,
91
- reason: `The assistant did not provide authorized change set "${changeSetId}".`
92
- };
93
- changeSets.push(candidate);
94
- }
95
- return {
96
- status: "completed",
97
- turnFinal,
98
- changeSets
99
- };
100
- };
101
- const resolveSurveyTurnOutcome = ({ applyResultsByChangeId, authorization }) => {
102
- if (authorization.status === "failed" || authorization.status === "aborted") return {
103
- status: authorization.status,
104
- terminationReason: authorization.turnFinal.terminationReason
105
- };
106
- if (authorization.status !== "completed") return null;
107
- const blockedChangeSetCount = authorization.turnFinal.blockedChangeSetIds?.length ?? 0;
108
- if (blockedChangeSetCount > 0) return {
109
- status: "blocked",
110
- blockedChangeSetCount
111
- };
112
- if (authorization.changeSets.length === 0) return null;
113
- const results = authorization.changeSets.map((change) => applyResultsByChangeId[change.id]);
114
- if (results.some((result) => result === void 0)) return null;
115
- const failedChangeSetCount = results.filter((result) => result.status !== "applied").length;
116
- if (failedChangeSetCount === 0) return null;
117
- return {
118
- status: "apply-failed",
119
- appliedChangeSetCount: results.length - failedChangeSetCount,
120
- failedChangeSetCount
121
- };
122
- };
123
- const getSurveyDocumentVersionFromSnapshot = (snapshot) => ({ ...snapshot.version });
124
- const createApplyReceipts = ({ afterSnapshot, beforeSnapshot, changeSets, results, turnFinal, createdAt = (/* @__PURE__ */ new Date()).toISOString() }) => {
125
- const beforeVersion = getSurveyDocumentVersionFromSnapshot(beforeSnapshot);
126
- const afterVersion = afterSnapshot ? getSurveyDocumentVersionFromSnapshot(afterSnapshot) : void 0;
127
- const resultsByChangeId = new Map(results.map((result) => [result.changeId, result]));
128
- return changeSets.map((changeSet) => {
129
- const result = resultsByChangeId.get(changeSet.id);
130
- const status = result?.status ?? "failed";
131
- return applyReceiptSchema.parse({
132
- protocolVersion: 1,
133
- turnId: turnFinal.turnId,
134
- changeSetId: changeSet.id,
135
- beforeVersion,
136
- ...status === "applied" && afterVersion ? { afterVersion } : {},
137
- status,
138
- operationCount: changeSet.operations.length,
139
- ...status === "applied" && afterVersion?.historyIndex !== void 0 ? { commit: { index: afterVersion.historyIndex } } : {},
140
- ...result?.issues ? { issues: result.issues } : {},
141
- createdAt
142
- });
143
- });
144
- };
145
- //#endregion
146
- //#region src/react/transport.ts
147
- const findLastUserMessage = (messages) => {
148
- for (let index = messages.length - 1; index >= 0; index -= 1) if (messages[index]?.role === "user") return messages[index];
149
- };
150
- const fetchSurveyAssistantChat = async (input, init) => {
151
- const response = await globalThis.fetch(input, init);
152
- if (!response.ok) await readSurveyAssistantJson(response);
153
- return response;
154
- };
155
- const createSurveyAssistantTransport = ({ api = "/api/survey-assistant/chat", resourceId, surveyId, threadId }) => new DefaultChatTransport({
156
- api,
157
- body: {
158
- resourceId,
159
- ...surveyId ? { surveyId } : {},
160
- threadId
161
- },
162
- fetch: fetchSurveyAssistantChat,
163
- prepareSendMessagesRequest: ({ api, body, credentials, headers, id, messageId, messages, trigger }) => {
164
- const latestUserMessage = findLastUserMessage(messages);
165
- return {
166
- api,
167
- credentials,
168
- headers,
169
- body: {
170
- ...body,
171
- id,
172
- messages: latestUserMessage ? [latestUserMessage] : [],
173
- trigger,
174
- messageId
175
- }
176
- };
177
- }
178
- });
179
- //#endregion
180
- //#region src/react/thread-client.ts
181
- const createSurveyAssistantThreadClient = (api = "/api/survey-assistant/threads") => ({
182
- async delete(threadId) {
183
- const response = await fetch(`${api}/${encodeURIComponent(threadId)}`, { method: "DELETE" });
184
- if (!response.ok) await readSurveyAssistantJson(response);
185
- },
186
- async create(title) {
187
- const body = await readSurveyAssistantJson(await fetch(api, {
188
- body: JSON.stringify(title ? { title } : {}),
189
- headers: { "content-type": "application/json" },
190
- method: "POST"
191
- }));
192
- return surveyAssistantThreadSchema.parse(body.thread);
193
- },
194
- async list() {
195
- const body = await readSurveyAssistantJson(await fetch(api, { cache: "no-store" }));
196
- return surveyAssistantThreadListSchema.parse(body).threads;
197
- },
198
- async listPage(options) {
199
- const search = new URLSearchParams();
200
- if (options?.cursor) search.set("cursor", options.cursor);
201
- const body = await readSurveyAssistantJson(await fetch(`${api}${search.size ? `?${search}` : ""}`, { cache: "no-store" }));
202
- return surveyAssistantThreadListSchema.parse(body);
203
- },
204
- async read(threadId, cursor) {
205
- const search = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
206
- const body = await readSurveyAssistantJson(await fetch(`${api}/${encodeURIComponent(threadId)}${search}`, { cache: "no-store" }));
207
- return surveyAssistantThreadTranscriptSchema.parse(body);
208
- },
209
- async rename(threadId, title) {
210
- const body = await readSurveyAssistantJson(await fetch(`${api}/${encodeURIComponent(threadId)}`, {
211
- body: JSON.stringify({
212
- mode: "manual",
213
- title
214
- }),
215
- headers: { "content-type": "application/json" },
216
- method: "PATCH"
217
- }));
218
- return surveyAssistantThreadSchema.parse(body.thread);
219
- },
220
- async setAutomaticTitle(threadId, title) {
221
- const body = await readSurveyAssistantJson(await fetch(`${api}/${encodeURIComponent(threadId)}`, {
222
- body: JSON.stringify({
223
- mode: "automatic",
224
- ...title ? { title } : {}
225
- }),
226
- headers: { "content-type": "application/json" },
227
- method: "PATCH"
228
- }));
229
- return surveyAssistantThreadSchema.parse(body.thread);
230
- }
231
- });
232
- //#endregion
233
- //#region src/react/model-catalog.ts
234
- const DEFAULT_MODEL_STORAGE_KEY = "survey-assistant-selected-model";
235
- const createSurveyAssistantModelCatalogClient = (api = "/api/survey-assistant/models") => ({ async get() {
236
- const body = await readSurveyAssistantJson(await fetch(api, { cache: "no-store" }), "The assistant model catalog could not be loaded.");
237
- return surveyAssistantModelCatalogSchema.parse(body);
238
- } });
239
- const useSurveyAssistantModelCatalog = ({ api, client: providedClient, onModelChange, storageKey = DEFAULT_MODEL_STORAGE_KEY } = {}) => {
240
- const [models, setModels] = useState([]);
241
- const [modelId, setModelId] = useState();
242
- const [error, setError] = useState(null);
243
- const [loading, setLoading] = useState(true);
244
- const [reloadToken, setReloadToken] = useState(0);
245
- useEffect(() => {
246
- let cancelled = false;
247
- const client = providedClient ?? createSurveyAssistantModelCatalogClient(api);
248
- setLoading(true);
249
- setError(null);
250
- client.get().then((catalog) => {
251
- if (cancelled) return;
252
- const rememberedId = storageKey && typeof window !== "undefined" ? window.localStorage.getItem(storageKey) ?? void 0 : void 0;
253
- const selectedId = rememberedId && catalog.models.some((model) => model.id === rememberedId) ? rememberedId : catalog.defaultModelId;
254
- setModels(catalog.models);
255
- setModelId(selectedId);
256
- onModelChange?.(selectedId);
257
- }).catch((reason) => {
258
- if (!cancelled) setError(reason instanceof Error ? reason : new Error(String(reason)));
259
- }).finally(() => {
260
- if (!cancelled) setLoading(false);
261
- });
262
- return () => {
263
- cancelled = true;
264
- };
265
- }, [
266
- api,
267
- onModelChange,
268
- providedClient,
269
- reloadToken,
270
- storageKey
271
- ]);
272
- const selectModel = useCallback((nextModelId) => {
273
- if (!models.some((model) => model.id === nextModelId)) return;
274
- setModelId(nextModelId);
275
- if (storageKey && typeof window !== "undefined") window.localStorage.setItem(storageKey, nextModelId);
276
- onModelChange?.(nextModelId);
277
- }, [
278
- models,
279
- onModelChange,
280
- storageKey
281
- ]);
282
- return {
283
- error,
284
- loading,
285
- modelId,
286
- models,
287
- reload: useCallback(() => setReloadToken((current) => current + 1), []),
288
- selectModel
289
- };
290
- };
291
- //#endregion
292
- //#region src/react/document-baseline.ts
293
- const STORAGE_PREFIX = "case-survey-assistant:settled-document:";
294
- const memoryBaselines = /* @__PURE__ */ new Map();
295
- const getStorageKey = (resourceId, threadId) => `${STORAGE_PREFIX}${encodeURIComponent(resourceId)}:${encodeURIComponent(threadId)}`;
296
- const getSessionStorage = () => {
297
- if (typeof document === "undefined") return void 0;
298
- try {
299
- return window.sessionStorage;
300
- } catch {
301
- return;
302
- }
303
- };
304
- const readSettledDocumentVersion = ({ resourceId, threadId }) => {
305
- const storageKey = getStorageKey(resourceId, threadId);
306
- const storedInMemory = memoryBaselines.get(storageKey);
307
- if (storedInMemory) return { ...storedInMemory };
308
- const serialized = getSessionStorage()?.getItem(storageKey);
309
- if (!serialized) return void 0;
310
- try {
311
- const parsed = surveyDocumentVersionSchema.safeParse(JSON.parse(serialized));
312
- if (!parsed.success) return void 0;
313
- memoryBaselines.set(storageKey, parsed.data);
314
- return { ...parsed.data };
315
- } catch {
316
- return;
317
- }
318
- };
319
- const writeSettledDocumentVersion = ({ resourceId, snapshot, threadId }) => {
320
- const storageKey = getStorageKey(resourceId, threadId);
321
- const version = surveyDocumentVersionSchema.parse(snapshot.version);
322
- memoryBaselines.set(storageKey, version);
323
- try {
324
- getSessionStorage()?.setItem(storageKey, JSON.stringify(version));
325
- } catch {}
326
- };
327
- //#endregion
328
- //#region src/react/use-survey-assistant-session.ts
329
- const createLocalId = (prefix) => {
330
- return `${prefix}-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
331
- };
332
- const compactOutcomeText = (value, maximum, fallback) => {
333
- const normalized = value.trim() || fallback;
334
- return normalized.length > maximum ? `${normalized.slice(0, maximum - 1)}…` : normalized;
335
- };
336
- const createApplyOutcome = ({ message, preparedChanges, results, status, turnId }) => surveyAssistantApplyOutcomeSchema.parse({
337
- protocolVersion: 1,
338
- turnId,
339
- status,
340
- message: compactOutcomeText(message, 2e3, "No lifecycle detail was reported."),
341
- ...results && results.length > 0 ? { results: results.slice(0, 100).map((result) => ({
342
- status: result.status,
343
- changeId: compactOutcomeText(result.changeId, 240, "unknown-change"),
344
- summary: compactOutcomeText(result.summary, 500, "Survey change"),
345
- message: compactOutcomeText(result.message, 2e3, "No apply detail was reported."),
346
- appliedOperations: result.appliedOperations
347
- })) } : {},
348
- ...preparedChanges && preparedChanges.length > 0 ? { preparedChanges: preparedChanges.slice(-24).map((change) => ({
349
- changeId: compactOutcomeText(change.id, 240, "unknown-change"),
350
- status: change.status,
351
- summary: compactOutcomeText(change.summary, 500, "Survey change"),
352
- operationCount: change.operations.length
353
- })) } : {},
354
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
355
- });
356
- const getAssistantTextParts = (message) => message.parts.filter((part) => part.type === "text" && part.text.trim().length > 0);
357
- const findLastAssistantTextMessageIndex = (messages, endExclusive = messages.length) => {
358
- for (let index = Math.min(messages.length, endExclusive) - 1; index >= 0; index -= 1) {
359
- const message = messages[index];
360
- if (message?.role === "assistant" && getAssistantTextParts(message).length > 0) return index;
361
- }
362
- return -1;
363
- };
364
- const findLastUserMessageIndex = (messages) => {
365
- for (let index = messages.length - 1; index >= 0; index -= 1) if (messages[index]?.role === "user") return index;
366
- return -1;
367
- };
368
- const pruneFailedMessagesForDisplay = (messages) => {
369
- const lastUserIndex = findLastUserMessageIndex(messages);
370
- const lastCompletedAssistantIndex = findLastAssistantTextMessageIndex(messages);
371
- if (lastUserIndex > lastCompletedAssistantIndex) return messages.slice(0, lastUserIndex + 1);
372
- return lastCompletedAssistantIndex >= 0 ? messages.slice(0, lastCompletedAssistantIndex + 1) : [];
373
- };
374
- const hasCompletedAssistantResponseAfterLastUser = (messages) => {
375
- const lastUserIndex = findLastUserMessageIndex(messages);
376
- return lastUserIndex >= 0 && findLastAssistantTextMessageIndex(messages) > lastUserIndex;
377
- };
378
- const collectChangeSetsAfterLastUser = (messages) => {
379
- const lastUserIndex = findLastUserMessageIndex(messages);
380
- return lastUserIndex < 0 ? [] : collectSurveyChangeSets(messages.slice(lastUserIndex + 1));
381
- };
382
- const getInitialSessionState = (messages) => {
383
- if (messages.length === 0) return "idle";
384
- const authorization = resolveLatestSurveyTurnAuthorization(messages);
385
- if (authorization.status === "failed") return "failed";
386
- if (authorization.status === "aborted") return "aborted";
387
- return "completed";
388
- };
389
- const resolveSurveyAssistantReviewDecision = async ({ changeSets, onReviewChangeSets, turnFinal }) => {
390
- if (!onReviewChangeSets) throw new Error("No survey assistant review adapter is available.");
391
- return onReviewChangeSets(changeSets, turnFinal);
392
- };
393
- const useSurveyAssistantSession = ({ api, applyPolicy, controller, initialMessages = [], modelId, onApplyReceipts, onSettleApply, onReviewChangeSets, onTurnActiveChange, resourceId: controlledResourceId, surveyId, threadId: controlledThreadId }) => {
394
- const [localResourceId] = useState(() => createLocalId("survey-assistant-local-user"));
395
- const [localThreadId] = useState(() => createLocalId("survey-assistant-local-thread"));
396
- const resourceId = controlledResourceId ?? localResourceId;
397
- const threadId = controlledThreadId ?? localThreadId;
398
- const applyOutcomeScopeKey = `${resourceId}\u0000${threadId}`;
399
- const [state, dispatch] = useReducer(reduceSurveyAssistantSessionState, initialMessages, getInitialSessionState);
400
- const [applyResultsByChangeId, setApplyResultsByChangeId] = useState({});
401
- const [authorizationError, setAuthorizationError] = useState(null);
402
- const handledTurnIdsRef = useRef(/* @__PURE__ */ new Set());
403
- const localTurnAwaitingApplyRef = useRef(false);
404
- const localTurnStreamStartedRef = useRef(false);
405
- const captureSettledVersionRef = useRef(false);
406
- const lastSubmittedUIContextRef = useRef(void 0);
407
- const lastSubmittedModelIdRef = useRef(void 0);
408
- const previousApplyOutcomesRef = useRef(/* @__PURE__ */ new Map());
409
- const { messages, setMessages, sendMessage, status, stop: stopChat, error, clearError, regenerate } = useChat({
410
- id: threadId,
411
- messages: initialMessages,
412
- transport: useMemo(() => createSurveyAssistantTransport({
413
- api,
414
- resourceId,
415
- surveyId,
416
- threadId
417
- }), [
418
- api,
419
- resourceId,
420
- surveyId,
421
- threadId
422
- ])
423
- });
424
- const isStreaming = status === "submitted" || status === "streaming";
425
- const isTurnActive = isSurveyAssistantTurnActive({
426
- isStreaming,
427
- state
428
- });
429
- const notifyTurnActiveChange = useEffectEvent((active) => {
430
- onTurnActiveChange?.(active);
431
- });
432
- const prependMessages = useCallback((olderMessages) => {
433
- setMessages((current) => {
434
- const currentIds = new Set(current.map((message) => message.id));
435
- return [...olderMessages.filter((message) => !currentIds.has(message.id)), ...current];
436
- });
437
- }, [setMessages]);
438
- const turnAuthorization = useMemo(() => resolveLatestSurveyTurnAuthorization(messages), [messages]);
439
- const latestTurnOutcome = useMemo(() => resolveSurveyTurnOutcome({
440
- applyResultsByChangeId,
441
- authorization: turnAuthorization
442
- }), [applyResultsByChangeId, turnAuthorization]);
443
- const hasUsefulLatestAssistantResponse = useMemo(() => hasCompletedAssistantResponseAfterLastUser(messages) || collectChangeSetsAfterLastUser(messages).length > 0, [messages]);
444
- const reportAuthorizationError = useCallback((message) => {
445
- queueMicrotask(() => {
446
- setAuthorizationError(message);
447
- dispatch({ type: "failed" });
448
- });
449
- }, []);
450
- useEffect(() => {
451
- if (status === "submitted") dispatch({ type: "submit" });
452
- if (status === "streaming") dispatch({ type: "stream-started" });
453
- }, [status]);
454
- useEffect(() => {
455
- notifyTurnActiveChange(isTurnActive);
456
- return () => {
457
- if (isTurnActive) notifyTurnActiveChange(false);
458
- };
459
- }, [isTurnActive]);
460
- useEffect(() => {
461
- if (!captureSettledVersionRef.current || isStreaming || state !== "completed" && state !== "failed" && state !== "aborted" && state !== "applied") return;
462
- captureSettledVersionRef.current = false;
463
- const snapshot = controller?.getSnapshot();
464
- if (snapshot) writeSettledDocumentVersion({
465
- resourceId,
466
- snapshot,
467
- threadId
468
- });
469
- }, [
470
- controller,
471
- isStreaming,
472
- resourceId,
473
- state,
474
- threadId
475
- ]);
476
- useEffect(() => {
477
- if (isStreaming) {
478
- localTurnStreamStartedRef.current = true;
479
- return;
480
- }
481
- if (!localTurnAwaitingApplyRef.current) return;
482
- if (turnAuthorization.status === "pending") {
483
- if (!localTurnStreamStartedRef.current) return;
484
- const timeoutId = window.setTimeout(() => {
485
- localTurnAwaitingApplyRef.current = false;
486
- localTurnStreamStartedRef.current = false;
487
- reportAuthorizationError("The assistant response ended without a valid turn final.");
488
- }, 100);
489
- return () => window.clearTimeout(timeoutId);
490
- }
491
- localTurnAwaitingApplyRef.current = false;
492
- localTurnStreamStartedRef.current = false;
493
- if (turnAuthorization.status === "invalid") {
494
- reportAuthorizationError(turnAuthorization.reason);
495
- return;
496
- }
497
- const { turnFinal } = turnAuthorization;
498
- if (handledTurnIdsRef.current.has(turnFinal.turnId)) return;
499
- handledTurnIdsRef.current.add(turnFinal.turnId);
500
- const preparedChanges = collectChangeSetsAfterLastUser(messages).filter((change) => change.turnId === turnFinal.turnId);
501
- if (turnAuthorization.status === "failed") {
502
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
503
- turnId: turnFinal.turnId,
504
- status: "not-applied",
505
- message: "The previous assistant turn failed before editor authorization. Every request-scoped draft change from that turn was discarded and was not applied.",
506
- preparedChanges
507
- }));
508
- dispatch({ type: "failed" });
509
- return;
510
- }
511
- if (turnAuthorization.status === "aborted") {
512
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
513
- turnId: turnFinal.turnId,
514
- status: "not-applied",
515
- message: "The previous assistant turn was interrupted before editor authorization. Every request-scoped draft change from that turn was discarded and was not applied.",
516
- preparedChanges
517
- }));
518
- dispatch({ type: "aborted" });
519
- return;
520
- }
521
- if (turnAuthorization.changeSets.length === 0) {
522
- if ((turnFinal.blockedChangeSetIds?.length ?? 0) > 0) previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
523
- turnId: turnFinal.turnId,
524
- status: "not-applied",
525
- message: "The previous assistant turn ended with unresolved rejected changes. No request-scoped draft change from that turn was authorized or applied.",
526
- preparedChanges
527
- }));
528
- dispatch({ type: "completed" });
529
- return;
530
- }
531
- if (applyPolicy === "deny") {
532
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
533
- turnId: turnFinal.turnId,
534
- status: "not-applied",
535
- message: "The host apply policy did not authorize editor mutations for this turn.",
536
- preparedChanges
537
- }));
538
- dispatch({ type: "completed" });
539
- return;
540
- }
541
- dispatch({ type: "apply-started" });
542
- (async () => {
543
- if (applyPolicy === "review") {
544
- let approved;
545
- try {
546
- approved = await resolveSurveyAssistantReviewDecision({
547
- changeSets: turnAuthorization.changeSets,
548
- onReviewChangeSets,
549
- turnFinal
550
- });
551
- } catch (error) {
552
- const message = error instanceof Error && error.message === "No survey assistant review adapter is available." ? error.message : "The survey assistant review could not be completed.";
553
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
554
- turnId: turnFinal.turnId,
555
- status: "not-applied",
556
- message,
557
- preparedChanges
558
- }));
559
- reportAuthorizationError(message);
560
- return;
561
- }
562
- if (!approved) {
563
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
564
- turnId: turnFinal.turnId,
565
- status: "not-applied",
566
- message: "The proposed editor changes were not approved during review.",
567
- preparedChanges
568
- }));
569
- dispatch({ type: "completed" });
570
- return;
571
- }
572
- }
573
- if (!controller?.applyAssistantChange && !controller?.applyAssistantChanges) {
574
- const message = "No survey editor apply adapter is available.";
575
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
576
- turnId: turnFinal.turnId,
577
- status: "apply-failed",
578
- message,
579
- preparedChanges
580
- }));
581
- reportAuthorizationError(message);
582
- return;
583
- }
584
- const beforeSnapshot = controller.getSnapshot();
585
- if (!beforeSnapshot) {
586
- const message = "The editor state was unavailable when applying the assistant change.";
587
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
588
- turnId: turnFinal.turnId,
589
- status: "apply-failed",
590
- message,
591
- preparedChanges
592
- }));
593
- reportAuthorizationError(message);
594
- return;
595
- }
596
- const result = controller.applyAssistantChanges?.(turnAuthorization.changeSets) ?? { results: turnAuthorization.changeSets.map((change) => controller.applyAssistantChange?.(change)).filter((applyResult) => Boolean(applyResult)) };
597
- queueMicrotask(() => setApplyResultsByChangeId((current) => ({
598
- ...current,
599
- ...Object.fromEntries(result.results.map((applyResult) => [applyResult.changeId, applyResult]))
600
- })));
601
- const receipts = createApplyReceipts({
602
- beforeSnapshot,
603
- afterSnapshot: controller.getSnapshot(),
604
- changeSets: turnAuthorization.changeSets,
605
- results: result.results,
606
- turnFinal
607
- });
608
- onApplyReceipts?.(receipts);
609
- if (!result.results.every((applyResult) => applyResult.status === "applied")) {
610
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
611
- turnId: turnFinal.turnId,
612
- status: "apply-failed",
613
- message: "One or more authorized survey changes could not be applied to the editor.",
614
- preparedChanges,
615
- results: result.results
616
- }));
617
- dispatch({ type: "completed" });
618
- return;
619
- }
620
- try {
621
- await onSettleApply?.({
622
- changeSets: turnAuthorization.changeSets,
623
- receipts,
624
- turnFinal
625
- });
626
- previousApplyOutcomesRef.current.delete(applyOutcomeScopeKey);
627
- dispatch({ type: "applied" });
628
- } catch {
629
- const message = "The assistant changes were applied locally but could not be settled by the host.";
630
- previousApplyOutcomesRef.current.set(applyOutcomeScopeKey, createApplyOutcome({
631
- turnId: turnFinal.turnId,
632
- status: "settle-failed",
633
- message,
634
- results: result.results
635
- }));
636
- reportAuthorizationError(message);
637
- }
638
- })();
639
- }, [
640
- applyPolicy,
641
- applyOutcomeScopeKey,
642
- controller,
643
- isStreaming,
644
- onApplyReceipts,
645
- onReviewChangeSets,
646
- onSettleApply,
647
- reportAuthorizationError,
648
- messages,
649
- turnAuthorization
650
- ]);
651
- useEffect(() => {
652
- if (!error) return;
653
- localTurnAwaitingApplyRef.current = false;
654
- localTurnStreamStartedRef.current = false;
655
- dispatch({ type: "failed" });
656
- if (!hasUsefulLatestAssistantResponse) setMessages(pruneFailedMessagesForDisplay);
657
- }, [
658
- error,
659
- hasUsefulLatestAssistantResponse,
660
- setMessages
661
- ]);
662
- const submit = useCallback(({ files, modelId: submittedModelId, text: rawText, uiContext }) => {
663
- const text = rawText.trim();
664
- if (!text && files.length === 0) return false;
665
- if (isStreaming || localTurnAwaitingApplyRef.current) return false;
666
- localTurnAwaitingApplyRef.current = true;
667
- localTurnStreamStartedRef.current = false;
668
- setAuthorizationError(null);
669
- if (authorizationError || error) setMessages(pruneFailedMessagesForDisplay);
670
- clearError();
671
- dispatch({ type: "submit" });
672
- captureSettledVersionRef.current = true;
673
- lastSubmittedUIContextRef.current = uiContext;
674
- lastSubmittedModelIdRef.current = submittedModelId ?? modelId;
675
- const snapshot = applySurveyAssistantLaunchUIContext(controller?.getSnapshot() ?? null, uiContext);
676
- sendMessage(files.length > 0 ? text ? {
677
- text,
678
- files
679
- } : { files } : { text }, { body: {
680
- snapshot,
681
- previousSettledVersion: readSettledDocumentVersion({
682
- resourceId,
683
- threadId
684
- }),
685
- previousApplyOutcome: previousApplyOutcomesRef.current.get(applyOutcomeScopeKey),
686
- ...lastSubmittedModelIdRef.current ? { modelId: lastSubmittedModelIdRef.current } : {}
687
- } });
688
- return true;
689
- }, [
690
- authorizationError,
691
- applyOutcomeScopeKey,
692
- clearError,
693
- controller,
694
- error,
695
- isStreaming,
696
- modelId,
697
- resourceId,
698
- sendMessage,
699
- setMessages,
700
- threadId
701
- ]);
702
- const stop = useCallback(() => {
703
- localTurnAwaitingApplyRef.current = false;
704
- localTurnStreamStartedRef.current = false;
705
- dispatch({ type: "aborted" });
706
- stopChat();
707
- }, [stopChat]);
708
- const retry = useCallback(() => {
709
- localTurnAwaitingApplyRef.current = true;
710
- localTurnStreamStartedRef.current = false;
711
- setAuthorizationError(null);
712
- clearError();
713
- dispatch({ type: "submit" });
714
- captureSettledVersionRef.current = true;
715
- const controllerSnapshot = controller?.getSnapshot() ?? null;
716
- const uiContext = lastSubmittedUIContextRef.current;
717
- regenerate({ body: {
718
- snapshot: applySurveyAssistantLaunchUIContext(controllerSnapshot, uiContext),
719
- previousSettledVersion: readSettledDocumentVersion({
720
- resourceId,
721
- threadId
722
- }),
723
- previousApplyOutcome: previousApplyOutcomesRef.current.get(applyOutcomeScopeKey),
724
- ...lastSubmittedModelIdRef.current ? { modelId: lastSubmittedModelIdRef.current } : {}
725
- } });
726
- }, [
727
- applyOutcomeScopeKey,
728
- clearError,
729
- controller,
730
- modelId,
731
- regenerate,
732
- resourceId,
733
- threadId
734
- ]);
735
- return {
736
- applyResultsByChangeId,
737
- authorizationError,
738
- dismissError: useCallback(() => {
739
- localTurnAwaitingApplyRef.current = false;
740
- localTurnStreamStartedRef.current = false;
741
- setMessages(pruneFailedMessagesForDisplay);
742
- setAuthorizationError(null);
743
- clearError();
744
- dispatch({
745
- type: "reset",
746
- hasTranscript: messages.length > 0
747
- });
748
- }, [
749
- clearError,
750
- messages.length,
751
- setMessages
752
- ]),
753
- error,
754
- isStreaming,
755
- latestTurnOutcome,
756
- messages,
757
- prependMessages,
758
- state,
759
- status,
760
- stop,
761
- submit,
762
- retry
763
- };
764
- };
765
- //#endregion
766
- export { createSurveyAssistantTransport as a, getSurveyDocumentVersionFromSnapshot as c, resolveSurveyTurnOutcome as d, unwrapSurveyAssistantToolOutput as f, createSurveyAssistantThreadClient as i, parseSurveyChangeSet as l, reduceSurveyAssistantSessionState as m, createSurveyAssistantModelCatalogClient as n, collectSurveyChangeSets as o, isSurveyAssistantTurnActive as p, useSurveyAssistantModelCatalog as r, createApplyReceipts as s, useSurveyAssistantSession as t, resolveLatestSurveyTurnAuthorization as u };
767
-
768
- //# sourceMappingURL=react-Dqlmasrs.mjs.map