@case-framework/survey-assistant 0.1.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 (98) hide show
  1. package/README.md +212 -0
  2. package/dist/authoring-references-Cy8J-DWt.d.mts +48 -0
  3. package/dist/authoring-references-Cy8J-DWt.d.mts.map +1 -0
  4. package/dist/authoring-references-X6uRaWuf.mjs +68 -0
  5. package/dist/authoring-references-X6uRaWuf.mjs.map +1 -0
  6. package/dist/capabilities-B6tvE7fK.mjs +66 -0
  7. package/dist/capabilities-B6tvE7fK.mjs.map +1 -0
  8. package/dist/capabilities-DvSKtjAw.d.mts +182 -0
  9. package/dist/capabilities-DvSKtjAw.d.mts.map +1 -0
  10. package/dist/capabilities-default.d.mts +15 -0
  11. package/dist/capabilities-default.d.mts.map +1 -0
  12. package/dist/capabilities-default.mjs +347 -0
  13. package/dist/capabilities-default.mjs.map +1 -0
  14. package/dist/constants-CFUWrRtl.mjs +7 -0
  15. package/dist/constants-CFUWrRtl.mjs.map +1 -0
  16. package/dist/constants-Jw0k8ojS.d.mts +67 -0
  17. package/dist/constants-Jw0k8ojS.d.mts.map +1 -0
  18. package/dist/controller-proxy-BRDusFiO.d.mts +57 -0
  19. package/dist/controller-proxy-BRDusFiO.d.mts.map +1 -0
  20. package/dist/controller-proxy-Duxo2IFE.mjs +92 -0
  21. package/dist/controller-proxy-Duxo2IFE.mjs.map +1 -0
  22. package/dist/digest-BsoVq49o.d.mts +9 -0
  23. package/dist/digest-BsoVq49o.d.mts.map +1 -0
  24. package/dist/digest.d.mts +2 -0
  25. package/dist/digest.mjs +20 -0
  26. package/dist/digest.mjs.map +1 -0
  27. package/dist/engine-sPvuixWg.mjs +4857 -0
  28. package/dist/engine-sPvuixWg.mjs.map +1 -0
  29. package/dist/engine.d.mts +3 -0
  30. package/dist/engine.mjs +4 -0
  31. package/dist/index-BWNHkpkh.d.mts +187 -0
  32. package/dist/index-BWNHkpkh.d.mts.map +1 -0
  33. package/dist/index-CUqbBjkc.d.mts +641 -0
  34. package/dist/index-CUqbBjkc.d.mts.map +1 -0
  35. package/dist/index-MpEMQzTm.d.mts +123 -0
  36. package/dist/index-MpEMQzTm.d.mts.map +1 -0
  37. package/dist/index-TFtvBiSQ.d.mts +1149 -0
  38. package/dist/index-TFtvBiSQ.d.mts.map +1 -0
  39. package/dist/memory-thread-repository-CJkkVE2-.mjs +718 -0
  40. package/dist/memory-thread-repository-CJkkVE2-.mjs.map +1 -0
  41. package/dist/protocol-DV0ka9WT.mjs +1032 -0
  42. package/dist/protocol-DV0ka9WT.mjs.map +1 -0
  43. package/dist/protocol.d.mts +3 -0
  44. package/dist/protocol.mjs +2 -0
  45. package/dist/react-VuYe-cPh.mjs +521 -0
  46. package/dist/react-VuYe-cPh.mjs.map +1 -0
  47. package/dist/react-integration.d.mts +2 -0
  48. package/dist/react-integration.mjs +3 -0
  49. package/dist/react.d.mts +3 -0
  50. package/dist/react.mjs +4 -0
  51. package/dist/references/assistant-operations.md +287 -0
  52. package/dist/references/core-rules.md +11 -0
  53. package/dist/references/embedded-forms.md +80 -0
  54. package/dist/references/expressions.md +301 -0
  55. package/dist/references/item-types.md +364 -0
  56. package/dist/references/localization.md +25 -0
  57. package/dist/references/rich-text-content.md +193 -0
  58. package/dist/references/source-material-surveys.md +60 -0
  59. package/dist/references/survey-data-model.md +109 -0
  60. package/dist/references/survey-quality.md +23 -0
  61. package/dist/request-context-jcmXT_Q2.mjs +44 -0
  62. package/dist/request-context-jcmXT_Q2.mjs.map +1 -0
  63. package/dist/request-guardrails-C59IWnH8.mjs +73 -0
  64. package/dist/request-guardrails-C59IWnH8.mjs.map +1 -0
  65. package/dist/server-agent.d.mts +131 -0
  66. package/dist/server-agent.d.mts.map +1 -0
  67. package/dist/server-agent.mjs +229 -0
  68. package/dist/server-agent.mjs.map +1 -0
  69. package/dist/server-runtime.d.mts +123 -0
  70. package/dist/server-runtime.d.mts.map +1 -0
  71. package/dist/server-runtime.mjs +296 -0
  72. package/dist/server-runtime.mjs.map +1 -0
  73. package/dist/server-tasks.d.mts +78 -0
  74. package/dist/server-tasks.d.mts.map +1 -0
  75. package/dist/server-tasks.mjs +253 -0
  76. package/dist/server-tasks.mjs.map +1 -0
  77. package/dist/server-tools.d.mts +2 -0
  78. package/dist/server-tools.mjs +2 -0
  79. package/dist/server.d.mts +52 -0
  80. package/dist/server.d.mts.map +1 -0
  81. package/dist/server.mjs +6 -0
  82. package/dist/storage-postgres.d.mts +37 -0
  83. package/dist/storage-postgres.d.mts.map +1 -0
  84. package/dist/storage-postgres.mjs +167 -0
  85. package/dist/storage-postgres.mjs.map +1 -0
  86. package/dist/thread-documents-DamIq9Od.d.mts +91 -0
  87. package/dist/thread-documents-DamIq9Od.d.mts.map +1 -0
  88. package/dist/thread-handlers-DpynUc64.d.mts +112 -0
  89. package/dist/thread-handlers-DpynUc64.d.mts.map +1 -0
  90. package/dist/tools-Hz2p7vyv.mjs +1879 -0
  91. package/dist/tools-Hz2p7vyv.mjs.map +1 -0
  92. package/dist/ui.css +2 -0
  93. package/dist/ui.d.mts +63 -0
  94. package/dist/ui.d.mts.map +1 -0
  95. package/dist/ui.mjs +2238 -0
  96. package/dist/ui.mjs.map +1 -0
  97. package/package.json +166 -0
  98. package/ui.css.d.ts +3 -0
@@ -0,0 +1,718 @@
1
+ import { D as createSurveyAssistantThreadInputSchema, Ot as serializeSurveyAssistantError, j as updateSurveyAssistantThreadInputSchema, n as SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE } from "./protocol-DV0ka9WT.mjs";
2
+ import { t as SurveyAssistantCapabilityMismatchError } from "./capabilities-B6tvE7fK.mjs";
3
+ import { o as TurnChangeCollector } from "./engine-sPvuixWg.mjs";
4
+ import "./constants-CFUWrRtl.mjs";
5
+ import { n as parseSurveyAssistantSnapshot, t as buildSurveyAssistantRequestContext } from "./request-context-jcmXT_Q2.mjs";
6
+ import { c as SurveyAssistantRequestError, l as readSurveyAssistantRequestJson, u as validateSurveyAssistantMessages } from "./request-guardrails-C59IWnH8.mjs";
7
+ import { createUIMessageStreamResponse } from "ai";
8
+ import { handleChatStream } from "@mastra/ai-sdk";
9
+ import { toAISdkMessages } from "@mastra/ai-sdk/ui";
10
+ //#region src/server/turn-stream.ts
11
+ const createTurnFinalChunk = (turnFinal) => ({
12
+ type: SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE,
13
+ id: turnFinal.turnId,
14
+ data: turnFinal
15
+ });
16
+ const createSurveyAssistantTurnStream = ({ changeCollector, onSettled, onUnexpectedError = () => "The survey assistant stream ended unexpectedly.", signal, stream }) => {
17
+ const reader = stream.getReader();
18
+ let settled = false;
19
+ const settle = () => {
20
+ if (settled) return;
21
+ settled = true;
22
+ onSettled?.();
23
+ };
24
+ return new ReadableStream({
25
+ async start(controller) {
26
+ let emittedTurnFinal = false;
27
+ let sawFinish = false;
28
+ let finishReason;
29
+ const emitTurnFinal = (status) => {
30
+ if (emittedTurnFinal) return;
31
+ emittedTurnFinal = true;
32
+ controller.enqueue(createTurnFinalChunk(changeCollector.finalize(status)));
33
+ };
34
+ try {
35
+ while (true) {
36
+ const { done, value } = await reader.read();
37
+ if (done) break;
38
+ if (value.type === SURVEY_ASSISTANT_TURN_FINAL_PART_TYPE) {
39
+ emitTurnFinal("failed");
40
+ controller.enqueue({
41
+ type: "error",
42
+ errorText: "The survey assistant returned an invalid duplicate turn final."
43
+ });
44
+ await reader.cancel("duplicate turn final");
45
+ break;
46
+ }
47
+ if (value.type === "error") {
48
+ emitTurnFinal("failed");
49
+ controller.enqueue(value);
50
+ continue;
51
+ }
52
+ if (value.type === "abort") {
53
+ emitTurnFinal("aborted");
54
+ controller.enqueue(value);
55
+ continue;
56
+ }
57
+ if (value.type === "finish") {
58
+ sawFinish = true;
59
+ finishReason = value.finishReason;
60
+ }
61
+ controller.enqueue(value);
62
+ }
63
+ if (!emittedTurnFinal) emitTurnFinal(signal?.aborted ? "aborted" : sawFinish && (finishReason === void 0 || finishReason === "stop") ? "completed" : "failed");
64
+ controller.close();
65
+ } catch (error) {
66
+ try {
67
+ emitTurnFinal(signal?.aborted ? "aborted" : "failed");
68
+ if (!signal?.aborted) controller.enqueue({
69
+ type: "error",
70
+ errorText: onUnexpectedError(error)
71
+ });
72
+ controller.close();
73
+ } catch {}
74
+ } finally {
75
+ reader.releaseLock();
76
+ settle();
77
+ }
78
+ },
79
+ async cancel(reason) {
80
+ changeCollector.finalize("aborted");
81
+ settle();
82
+ await reader.cancel(reason);
83
+ }
84
+ });
85
+ };
86
+ //#endregion
87
+ //#region src/server/thread-documents.ts
88
+ const parseFileDataUrl = (url) => {
89
+ const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/.exec(url);
90
+ return match ? {
91
+ mediaType: match[1],
92
+ data: match[2]
93
+ } : null;
94
+ };
95
+ const getBase64Bytes = (data) => {
96
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
97
+ return Math.max(0, Math.floor(data.length * 3 / 4) - padding);
98
+ };
99
+ const registerSurveyAssistantThreadAttachments = async ({ messages, repository, resourceId, threadId }) => {
100
+ const registered = [];
101
+ const currentMessage = getCurrentUserMessage(messages);
102
+ for (const message of currentMessage ? [currentMessage] : []) for (const part of message.parts) {
103
+ if (part.type !== "file" || typeof part.url !== "string") continue;
104
+ const parsed = parseFileDataUrl(part.url);
105
+ if (!parsed) continue;
106
+ registered.push(await repository.put({
107
+ resourceId,
108
+ threadId,
109
+ name: part.filename?.trim() || "Attached document",
110
+ mediaType: parsed.mediaType,
111
+ data: parsed.data,
112
+ bytes: getBase64Bytes(parsed.data)
113
+ }));
114
+ }
115
+ return registered;
116
+ };
117
+ const hasMatchingCurrentFile = (message, document) => message.parts.some((part) => part.type === "file" && part.filename === document.name && part.mediaType === document.mediaType);
118
+ const getCurrentUserMessage = (messages) => {
119
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
120
+ const message = messages[index];
121
+ if (message?.role === "user") return message;
122
+ }
123
+ };
124
+ const removeHistoricalSurveyAssistantAttachments = (messages) => {
125
+ const current = getCurrentUserMessage(messages);
126
+ return messages.map((message) => {
127
+ if (message === current) return message;
128
+ const parts = message.parts.filter((part) => part.type !== "file");
129
+ return parts.length === message.parts.length ? message : {
130
+ ...message,
131
+ parts
132
+ };
133
+ });
134
+ };
135
+ const getSurveyAssistantReplayableDocuments = (documents, messages) => {
136
+ const current = getCurrentUserMessage(messages);
137
+ return current ? documents.filter((document) => !hasMatchingCurrentFile(current, document)) : [];
138
+ };
139
+ const loadSurveyAssistantThreadAttachments = async ({ documents, messages, repository, resourceId, threadId }) => {
140
+ return loadSurveyAssistantDocuments({
141
+ documents: getSurveyAssistantReplayableDocuments(documents, messages),
142
+ repository,
143
+ resourceId,
144
+ threadId
145
+ });
146
+ };
147
+ const loadSurveyAssistantDocuments = async ({ documents, repository, resourceId, threadId }) => {
148
+ const attachments = [];
149
+ for (const document of documents) {
150
+ const content = await repository.read({
151
+ resourceId,
152
+ threadId,
153
+ documentId: document.id
154
+ });
155
+ if (!content) continue;
156
+ attachments.push({
157
+ filename: content.name,
158
+ mediaType: content.mediaType,
159
+ data: content.data,
160
+ contentHash: content.contentHash
161
+ });
162
+ }
163
+ return attachments;
164
+ };
165
+ //#endregion
166
+ //#region src/server/handler.ts
167
+ const DEFAULT_MAX_STEPS = 48;
168
+ const DEFAULT_MAX_CONCURRENT_REQUESTS = 4;
169
+ const MAX_USER_ERROR_MESSAGE_LENGTH = 700;
170
+ const MAX_ERROR_CAUSE_DEPTH = 5;
171
+ var SurveyAssistantAuthorizationError = class extends Error {
172
+ status;
173
+ constructor(message = "The survey assistant request is not authorized.", status = 403) {
174
+ super(message);
175
+ this.name = "SurveyAssistantAuthorizationError";
176
+ this.status = status;
177
+ }
178
+ };
179
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
180
+ const optionalRequestedId = (value) => {
181
+ if (typeof value !== "string") return void 0;
182
+ const normalized = value.trim();
183
+ return normalized.length > 0 && normalized.length <= 240 ? normalized : void 0;
184
+ };
185
+ const validateAuthorizedIdentity = (identity) => {
186
+ if (!identity || identity.authority !== "authenticated-host" && identity.authority !== "experimental-client-request" || typeof identity.resourceId !== "string" || identity.resourceId.trim().length === 0 || identity.resourceId.length > 240 || typeof identity.threadId !== "string" || identity.threadId.trim().length === 0 || identity.threadId.length > 240) throw new SurveyAssistantAuthorizationError("The host returned an invalid survey assistant identity.", 500);
187
+ return {
188
+ authority: identity.authority,
189
+ resourceId: identity.resourceId.trim(),
190
+ threadId: identity.threadId.trim()
191
+ };
192
+ };
193
+ const validateModelSelection = (selection) => {
194
+ if (!selection) return null;
195
+ const id = optionalRequestedId(selection.id);
196
+ const model = optionalRequestedId(selection.model);
197
+ return id && model ? {
198
+ id,
199
+ model
200
+ } : null;
201
+ };
202
+ const readNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
203
+ const recordTurnStreamPart = (metrics, part) => {
204
+ if (!isRecord(part)) return;
205
+ if (part.type === "tool-call") metrics.toolCalls += 1;
206
+ if (part.type !== "finish") return;
207
+ const payload = isRecord(part.payload) ? part.payload : void 0;
208
+ const output = isRecord(payload?.output) ? payload.output : void 0;
209
+ const usage = isRecord(output?.usage) ? output.usage : void 0;
210
+ if (!usage) return;
211
+ metrics.inputTokens = readNumber(usage.inputTokens);
212
+ metrics.outputTokens = readNumber(usage.outputTokens);
213
+ metrics.totalTokens = readNumber(usage.totalTokens);
214
+ metrics.reasoningTokens = readNumber(usage.reasoningTokens);
215
+ metrics.cachedInputTokens = readNumber(usage.cachedInputTokens);
216
+ };
217
+ const unwrapStreamError = (error) => {
218
+ if (isRecord(error) && error.error !== void 0) return error.error;
219
+ return error;
220
+ };
221
+ const describeErrorLevel = (value) => {
222
+ if (value instanceof Error) {
223
+ const properties = Object.fromEntries(Object.getOwnPropertyNames(value).filter((key) => key !== "stack").map((key) => [key, value[key]]));
224
+ return {
225
+ name: value.name,
226
+ message: value.message,
227
+ stack: value.stack,
228
+ ...properties
229
+ };
230
+ }
231
+ if (isRecord(value)) return {
232
+ constructorName: value.constructor?.name,
233
+ ...Object.fromEntries(Object.getOwnPropertyNames(value).map((key) => [key, value[key]]))
234
+ };
235
+ return { value };
236
+ };
237
+ const describeSurveyAssistantStreamError = (error) => {
238
+ const levels = [];
239
+ let current = unwrapStreamError(error);
240
+ for (let depth = 0; depth < MAX_ERROR_CAUSE_DEPTH && current !== void 0 && current !== null; depth += 1) {
241
+ const level = describeErrorLevel(current);
242
+ levels.push(level);
243
+ current = level.cause;
244
+ }
245
+ return levels.length === 1 ? levels[0] : { levels };
246
+ };
247
+ const clampUserErrorMessage = (message) => {
248
+ const normalized = message.replace(/\s+/g, " ").trim();
249
+ return normalized.length <= MAX_USER_ERROR_MESSAGE_LENGTH ? normalized : `${normalized.slice(0, MAX_USER_ERROR_MESSAGE_LENGTH - 1)}…`;
250
+ };
251
+ const getProviderModelName = (message) => message.match(/\bmodel\s+[`'"]([^`'"]+)[`'"]/i)?.[1]?.trim();
252
+ const classifyStreamError = (message, statusCode) => {
253
+ if (statusCode === 401 || statusCode === 403) return "authorization_failed";
254
+ if (statusCode === 429) return "rate_limited";
255
+ if (/\bmodel\b[\s\S]{0,160}\b(?:limited preview|not available)\b/i.test(message) || /\blimited preview\b[\s\S]{0,160}\bmodel\b/i.test(message)) return "model_unavailable";
256
+ if (/\b(?:rate limit|too many requests|request limit)\b/i.test(message)) return "rate_limited";
257
+ if (/\b(?:invalid api key|incorrect api key|authentication|unauthorized|forbidden)\b/i.test(message)) return "authorization_failed";
258
+ if (/\b(?:attachment|document|file)\b[\s\S]{0,120}\b(?:missing|unavailable|not found|failed)\b/i.test(message)) return "attachment_unavailable";
259
+ return "request_failed";
260
+ };
261
+ const formatKnownProviderError = (message) => {
262
+ const normalized = clampUserErrorMessage(message);
263
+ if (/\bmodel\b[\s\S]{0,160}\b(?:limited preview|not available)\b/i.test(normalized) || /\blimited preview\b[\s\S]{0,160}\bmodel\b/i.test(normalized)) {
264
+ const modelName = getProviderModelName(normalized);
265
+ return `The configured model${modelName ? ` \`${modelName}\`` : ""} is not available on this account.`;
266
+ }
267
+ if (/\b(?:rate limit|too many requests|request limit)\b/i.test(normalized)) return "The AI provider is temporarily limiting requests.";
268
+ if (/\b(?:invalid api key|incorrect api key|authentication|unauthorized|forbidden)\b/i.test(normalized)) return "The AI provider rejected the assistant credentials.";
269
+ return normalized;
270
+ };
271
+ const parseProviderResponseBodyMessage = (responseBody) => {
272
+ if (typeof responseBody !== "string" || responseBody.trim().length === 0) return void 0;
273
+ try {
274
+ const parsed = JSON.parse(responseBody);
275
+ if (isRecord(parsed) && isRecord(parsed.error) && typeof parsed.error.message === "string") return parsed.error.message;
276
+ } catch {
277
+ return responseBody;
278
+ }
279
+ return responseBody;
280
+ };
281
+ const extractUserErrorMessageFromLevel = (level) => {
282
+ if (isRecord(level.data) && isRecord(level.data.error) && typeof level.data.error.message === "string") return level.data.error.message;
283
+ return parseProviderResponseBodyMessage(level.responseBody) ?? (typeof level.message === "string" ? level.message : void 0);
284
+ };
285
+ const formatSurveyAssistantStreamError = (error) => {
286
+ const describedError = describeSurveyAssistantStreamError(error);
287
+ const levels = Array.isArray(describedError.levels) ? describedError.levels : [describedError];
288
+ const messageLevel = levels.find((level) => isRecord(level) && Boolean(extractUserErrorMessageFromLevel(level)));
289
+ const statusLevel = levels.find((level) => isRecord(level) && level.statusCode !== void 0);
290
+ const statusCode = Number(statusLevel?.statusCode);
291
+ const detail = messageLevel ? extractUserErrorMessageFromLevel(messageLevel) : void 0;
292
+ const formattedDetail = detail ? formatKnownProviderError(detail) : void 0;
293
+ return serializeSurveyAssistantError({
294
+ code: classifyStreamError(formattedDetail ?? "", Number.isFinite(statusCode) ? statusCode : void 0),
295
+ ...formattedDetail ? { detail: formattedDetail } : {}
296
+ });
297
+ };
298
+ const requestErrorResponse = (error, fallback) => {
299
+ const requestError = error instanceof SurveyAssistantRequestError ? error : new SurveyAssistantRequestError(fallback);
300
+ return Response.json({
301
+ code: "request_failed",
302
+ error: requestError.message
303
+ }, { status: requestError.status });
304
+ };
305
+ const createSurveyAssistantChatHandler = (options) => {
306
+ const maxConcurrentRequests = Number.isInteger(options.maxConcurrentRequests) && (options.maxConcurrentRequests ?? 0) > 0 ? options.maxConcurrentRequests : DEFAULT_MAX_CONCURRENT_REQUESTS;
307
+ const maxSteps = Number.isInteger(options.maxSteps) && (options.maxSteps ?? 0) > 0 ? options.maxSteps : DEFAULT_MAX_STEPS;
308
+ let activeRequestCount = 0;
309
+ const acquireRequestSlot = () => {
310
+ if (activeRequestCount >= maxConcurrentRequests) return null;
311
+ activeRequestCount += 1;
312
+ let released = false;
313
+ return () => {
314
+ if (released) return;
315
+ released = true;
316
+ activeRequestCount = Math.max(0, activeRequestCount - 1);
317
+ };
318
+ };
319
+ return async (request) => {
320
+ if (options.isEnabled && !await options.isEnabled(request)) return Response.json({
321
+ code: "authorization_failed",
322
+ error: "Survey assistant is disabled."
323
+ }, { status: 404 });
324
+ let rawParams;
325
+ try {
326
+ rawParams = await readSurveyAssistantRequestJson(request);
327
+ } catch (error) {
328
+ return requestErrorResponse(error, "The survey assistant request could not be read.");
329
+ }
330
+ const params = isRecord(rawParams) ? rawParams : {};
331
+ let identity;
332
+ try {
333
+ identity = validateAuthorizedIdentity(await options.authorize({
334
+ request,
335
+ requestedResourceId: optionalRequestedId(params.resourceId),
336
+ requestedThreadId: optionalRequestedId(params.threadId ?? params.id),
337
+ requestedSurveyId: optionalRequestedId(params.surveyId)
338
+ }));
339
+ } catch (error) {
340
+ const authorizationError = error instanceof SurveyAssistantAuthorizationError ? error : new SurveyAssistantAuthorizationError();
341
+ return Response.json({
342
+ code: "authorization_failed",
343
+ error: authorizationError.message
344
+ }, { status: authorizationError.status });
345
+ }
346
+ let messages;
347
+ try {
348
+ messages = validateSurveyAssistantMessages(params.messages);
349
+ } catch (error) {
350
+ return requestErrorResponse(error, "The survey assistant messages are invalid.");
351
+ }
352
+ let modelSelection;
353
+ if (options.resolveModel) {
354
+ const requestedModelId = optionalRequestedId(params.modelId);
355
+ if (params.modelId !== void 0 && !requestedModelId) return Response.json({
356
+ code: "model_unavailable",
357
+ error: "Invalid survey assistant model selection."
358
+ }, { status: 400 });
359
+ try {
360
+ modelSelection = validateModelSelection(await options.resolveModel({
361
+ identity,
362
+ request,
363
+ requestedModelId
364
+ })) ?? void 0;
365
+ } catch (error) {
366
+ options.onStreamError?.(error, {
367
+ resourceId: identity.resourceId,
368
+ threadId: identity.threadId
369
+ });
370
+ return Response.json({
371
+ code: "model_unavailable",
372
+ error: "The survey assistant model catalog is unavailable."
373
+ }, { status: 503 });
374
+ }
375
+ if (!modelSelection) return Response.json({
376
+ code: "model_unavailable",
377
+ error: "The selected survey assistant model is not available."
378
+ }, { status: 400 });
379
+ }
380
+ let snapshot;
381
+ try {
382
+ snapshot = parseSurveyAssistantSnapshot(params.snapshot);
383
+ } catch {
384
+ return Response.json({
385
+ code: "request_failed",
386
+ error: "Invalid survey assistant snapshot."
387
+ }, { status: 400 });
388
+ }
389
+ const readinessError = await options.getReadinessError?.(request);
390
+ if (readinessError) return Response.json({
391
+ code: "request_failed",
392
+ error: readinessError
393
+ }, { status: 503 });
394
+ const releaseRequestSlot = acquireRequestSlot();
395
+ if (!releaseRequestSlot) return Response.json({
396
+ code: "rate_limited",
397
+ error: "The survey assistant is handling too many requests. Try again shortly."
398
+ }, { status: 429 });
399
+ const changeCollector = new TurnChangeCollector();
400
+ let requestContext;
401
+ try {
402
+ let attachmentAccess;
403
+ let modelAttachments = [];
404
+ if (options.threadDocuments) {
405
+ const repository = options.threadDocuments;
406
+ const registered = await registerSurveyAssistantThreadAttachments({
407
+ messages,
408
+ repository,
409
+ resourceId: identity.resourceId,
410
+ threadId: identity.threadId
411
+ });
412
+ const manifest = await repository.list({
413
+ resourceId: identity.resourceId,
414
+ threadId: identity.threadId
415
+ });
416
+ modelAttachments = await loadSurveyAssistantDocuments({
417
+ documents: registered,
418
+ repository,
419
+ resourceId: identity.resourceId,
420
+ threadId: identity.threadId
421
+ });
422
+ messages = removeHistoricalSurveyAssistantAttachments(messages);
423
+ const registeredIds = new Set(registered.map((document) => document.id));
424
+ const replayableDocuments = getSurveyAssistantReplayableDocuments(manifest.filter((document) => !registeredIds.has(document.id)), messages);
425
+ const documents = [...registered, ...replayableDocuments];
426
+ if (documents.length > 0) attachmentAccess = {
427
+ documents,
428
+ currentDocumentIds: registered.map((document) => document.id),
429
+ selectedAttachments: [],
430
+ load: (documentIds) => loadSurveyAssistantDocuments({
431
+ documents: documents.filter((document) => documentIds.includes(document.id)),
432
+ repository,
433
+ resourceId: identity.resourceId,
434
+ threadId: identity.threadId
435
+ })
436
+ };
437
+ }
438
+ requestContext = buildSurveyAssistantRequestContext({
439
+ changeCollector,
440
+ identity,
441
+ snapshot,
442
+ snapshotSource: snapshot ? "full" : "missing",
443
+ trustedCapabilities: options.trustedCapabilities,
444
+ attachmentAccess,
445
+ modelAttachments,
446
+ modelSelection
447
+ });
448
+ } catch (error) {
449
+ releaseRequestSlot();
450
+ if (error instanceof SurveyAssistantRequestError) return requestErrorResponse(error, "The thread attachments could not be prepared.");
451
+ if (error instanceof SurveyAssistantCapabilityMismatchError) return Response.json({
452
+ error: error.message,
453
+ code: error.code
454
+ }, { status: 409 });
455
+ options.onStreamError?.(error, {
456
+ resourceId: identity.resourceId,
457
+ threadId: identity.threadId
458
+ });
459
+ return Response.json({
460
+ code: "attachment_unavailable",
461
+ error: "The survey assistant could not prepare the thread's reference documents."
462
+ }, { status: 503 });
463
+ }
464
+ const metrics = {
465
+ snapshotBytes: snapshot ? new TextEncoder().encode(JSON.stringify(snapshot)).byteLength : 0,
466
+ toolCalls: 0
467
+ };
468
+ const streamErrorContext = {
469
+ resourceId: identity.resourceId,
470
+ threadId: identity.threadId
471
+ };
472
+ const handleStreamError = (error) => {
473
+ options.onStreamError?.(error, streamErrorContext);
474
+ return options.formatStreamError?.(error, streamErrorContext) ?? formatSurveyAssistantStreamError(error);
475
+ };
476
+ let stream;
477
+ try {
478
+ stream = await handleChatStream({
479
+ mastra: typeof options.mastra === "function" ? await options.mastra() : options.mastra,
480
+ agentId: options.agentId ?? "survey-assistant-agent",
481
+ version: "v6",
482
+ params: {
483
+ messages,
484
+ ...params.trigger ? { trigger: params.trigger } : {},
485
+ requestContext,
486
+ runId: changeCollector.turnId,
487
+ abortSignal: request.signal,
488
+ onAbort: () => {
489
+ changeCollector.finalize("aborted");
490
+ },
491
+ onError: () => {
492
+ changeCollector.finalize("failed");
493
+ },
494
+ memory: {
495
+ thread: identity.threadId,
496
+ resource: identity.resourceId
497
+ }
498
+ },
499
+ defaultOptions: {
500
+ activeTools: [...options.activeTools],
501
+ maxSteps,
502
+ ...options.providerOptions ? { providerOptions: options.providerOptions } : {}
503
+ },
504
+ sendReasoning: options.sendReasoning ?? true,
505
+ messageMetadata: ({ part }) => {
506
+ recordTurnStreamPart(metrics, part);
507
+ },
508
+ onError: (error) => {
509
+ changeCollector.finalize("failed");
510
+ return handleStreamError(error);
511
+ }
512
+ });
513
+ } catch (error) {
514
+ changeCollector.finalize(request.signal.aborted ? "aborted" : "failed");
515
+ releaseRequestSlot();
516
+ return Response.json({ error: handleStreamError(error) }, { status: 503 });
517
+ }
518
+ return createUIMessageStreamResponse({ stream: createSurveyAssistantTurnStream({
519
+ stream,
520
+ changeCollector,
521
+ signal: request.signal,
522
+ onUnexpectedError: handleStreamError,
523
+ onSettled: () => {
524
+ releaseRequestSlot();
525
+ options.onTurnMetrics?.({
526
+ metrics,
527
+ provider: options.providerName,
528
+ turnId: changeCollector.turnId
529
+ });
530
+ }
531
+ }) });
532
+ };
533
+ };
534
+ //#endregion
535
+ //#region src/server/thread-handlers.ts
536
+ const DEFAULT_TITLE = "New chat";
537
+ const MANUAL_TITLE_METADATA_KEY = "surveyAssistant.manualTitle";
538
+ const AUTOMATIC_TITLE_METADATA_KEY = "surveyAssistant.automaticTitle";
539
+ const getMetadataString = (metadata, key) => {
540
+ const value = metadata?.[key];
541
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
542
+ };
543
+ const getRawTitle = (thread) => thread.title?.trim() || DEFAULT_TITLE;
544
+ const getAutomaticTitle = (thread, manualTitle) => {
545
+ const rawTitle = thread.title?.trim();
546
+ if (rawTitle && rawTitle !== manualTitle) return rawTitle;
547
+ return getMetadataString(thread.metadata, AUTOMATIC_TITLE_METADATA_KEY) ?? DEFAULT_TITLE;
548
+ };
549
+ const asThread = (thread) => {
550
+ const manualTitle = getMetadataString(thread.metadata, MANUAL_TITLE_METADATA_KEY);
551
+ return {
552
+ id: thread.id,
553
+ title: manualTitle ?? getRawTitle(thread),
554
+ titleMode: manualTitle ? "manual" : "automatic",
555
+ createdAt: thread.createdAt.toISOString(),
556
+ updatedAt: thread.updatedAt.toISOString()
557
+ };
558
+ };
559
+ const json = (body, status = 200) => Response.json(body, { status });
560
+ const errorResponse = (error) => {
561
+ const message = error instanceof Error ? error.message : "The chat request could not be completed.";
562
+ const status = typeof error === "object" && error !== null && "status" in error && typeof error.status === "number" ? error.status : 400;
563
+ return json({
564
+ code: status === 401 || status === 403 ? "authorization_failed" : "request_failed",
565
+ error: message
566
+ }, status);
567
+ };
568
+ const ownsThread = (thread, identity) => thread.resourceId === identity.resourceId && Object.entries(identity.metadata).every(([key, value]) => thread.metadata?.[key] === value);
569
+ const getOwnedThread = async (repository, identity, threadId) => {
570
+ const thread = await repository.get(threadId);
571
+ return thread && ownsThread(thread, identity) ? thread : null;
572
+ };
573
+ const createSurveyAssistantThreadHandlers = ({ authorize, repository }) => {
574
+ const list = async (request) => {
575
+ try {
576
+ const identity = await authorize(request);
577
+ return json({ threads: (await repository.list(identity.resourceId, identity.metadata)).filter((thread) => ownsThread(thread, identity) && typeof thread.metadata?.archivedAt !== "string").map(asThread) });
578
+ } catch (error) {
579
+ return errorResponse(error);
580
+ }
581
+ };
582
+ const create = async (request) => {
583
+ try {
584
+ const [identity, input] = await Promise.all([authorize(request), request.json().catch(() => ({}))]);
585
+ const parsed = createSurveyAssistantThreadInputSchema.parse(input);
586
+ const metadata = { ...identity.metadata };
587
+ if (parsed.title) {
588
+ metadata[MANUAL_TITLE_METADATA_KEY] = parsed.title;
589
+ metadata[AUTOMATIC_TITLE_METADATA_KEY] = DEFAULT_TITLE;
590
+ }
591
+ return json({ thread: asThread(await repository.create({
592
+ resourceId: identity.resourceId,
593
+ title: parsed.title ?? DEFAULT_TITLE,
594
+ metadata
595
+ })) }, 201);
596
+ } catch (error) {
597
+ return errorResponse(error);
598
+ }
599
+ };
600
+ const read = async (request, threadId) => {
601
+ try {
602
+ const identity = await authorize(request);
603
+ const thread = await getOwnedThread(repository, identity, threadId);
604
+ if (!thread || typeof thread.metadata?.archivedAt === "string") return json({ error: "Chat not found." }, 404);
605
+ return json({
606
+ thread: asThread(thread),
607
+ messages: await repository.readMessages(threadId, identity.resourceId)
608
+ });
609
+ } catch (error) {
610
+ return errorResponse(error);
611
+ }
612
+ };
613
+ const update = async (request, threadId) => {
614
+ try {
615
+ const [identity, input] = await Promise.all([authorize(request), request.json()]);
616
+ const thread = await getOwnedThread(repository, identity, threadId);
617
+ if (!thread || typeof thread.metadata?.archivedAt === "string") return json({ error: "Chat not found." }, 404);
618
+ const parsed = updateSurveyAssistantThreadInputSchema.parse(input);
619
+ const metadata = { ...thread.metadata ?? identity.metadata };
620
+ let title;
621
+ if (parsed.mode === "automatic") {
622
+ const manualTitle = getMetadataString(metadata, MANUAL_TITLE_METADATA_KEY);
623
+ title = parsed.title ?? (manualTitle ? getAutomaticTitle(thread, manualTitle) : getRawTitle(thread));
624
+ metadata[MANUAL_TITLE_METADATA_KEY] = null;
625
+ metadata[AUTOMATIC_TITLE_METADATA_KEY] = null;
626
+ } else {
627
+ const previousManualTitle = getMetadataString(metadata, MANUAL_TITLE_METADATA_KEY);
628
+ const automaticTitle = previousManualTitle ? getAutomaticTitle(thread, previousManualTitle) : getRawTitle(thread);
629
+ title = parsed.title;
630
+ metadata[MANUAL_TITLE_METADATA_KEY] = title;
631
+ metadata[AUTOMATIC_TITLE_METADATA_KEY] = automaticTitle;
632
+ }
633
+ return json({ thread: asThread(await repository.update({
634
+ id: thread.id,
635
+ title,
636
+ metadata
637
+ })) });
638
+ } catch (error) {
639
+ return errorResponse(error);
640
+ }
641
+ };
642
+ const deleteThread = async (request, threadId) => {
643
+ try {
644
+ const thread = await getOwnedThread(repository, await authorize(request), threadId);
645
+ if (!thread) return json({ error: "Chat not found." }, 404);
646
+ await repository.delete(thread.id);
647
+ return new Response(null, { status: 204 });
648
+ } catch (error) {
649
+ return errorResponse(error);
650
+ }
651
+ };
652
+ return {
653
+ create,
654
+ delete: deleteThread,
655
+ list,
656
+ read,
657
+ update
658
+ };
659
+ };
660
+ //#endregion
661
+ //#region src/server/memory-thread-repository.ts
662
+ const createMastraMemoryThreadRepository = ({ getMemory, threadDocuments }) => ({
663
+ async create({ metadata, resourceId, title }) {
664
+ return (await getMemory()).createThread({
665
+ metadata,
666
+ resourceId,
667
+ title,
668
+ saveThread: true
669
+ });
670
+ },
671
+ async get(threadId) {
672
+ return (await getMemory()).getThreadById({ threadId });
673
+ },
674
+ async list(resourceId, metadata) {
675
+ return (await (await getMemory()).listThreads({
676
+ filter: {
677
+ metadata,
678
+ resourceId
679
+ },
680
+ orderBy: {
681
+ field: "updatedAt",
682
+ direction: "DESC"
683
+ },
684
+ perPage: false
685
+ })).threads;
686
+ },
687
+ async readMessages(threadId, resourceId) {
688
+ return toAISdkMessages((await (await getMemory()).recall({
689
+ threadId,
690
+ resourceId,
691
+ perPage: false,
692
+ orderBy: {
693
+ field: "createdAt",
694
+ direction: "ASC"
695
+ }
696
+ })).messages, { version: "v6" });
697
+ },
698
+ async delete(threadId) {
699
+ const memory = await getMemory();
700
+ const thread = await memory.getThreadById({ threadId });
701
+ if (thread && threadDocuments) await threadDocuments.deleteThread({
702
+ resourceId: thread.resourceId,
703
+ threadId
704
+ });
705
+ await memory.deleteThread(threadId);
706
+ },
707
+ async update({ id, metadata, title }) {
708
+ return (await getMemory()).updateThread({
709
+ id,
710
+ metadata,
711
+ title
712
+ });
713
+ }
714
+ });
715
+ //#endregion
716
+ export { describeSurveyAssistantStreamError as a, loadSurveyAssistantDocuments as c, removeHistoricalSurveyAssistantAttachments as d, createSurveyAssistantTurnStream as f, createSurveyAssistantChatHandler as i, loadSurveyAssistantThreadAttachments as l, createSurveyAssistantThreadHandlers as n, formatSurveyAssistantStreamError as o, SurveyAssistantAuthorizationError as r, getSurveyAssistantReplayableDocuments as s, createMastraMemoryThreadRepository as t, registerSurveyAssistantThreadAttachments as u };
717
+
718
+ //# sourceMappingURL=memory-thread-repository-CJkkVE2-.mjs.map