@case-framework/survey-assistant 0.7.0 → 0.9.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/README.md +58 -3
- package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
- package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
- package/dist/{authoring-references-BJQ_1nMX.mjs → authoring-references-CmwLc_C8.mjs} +49 -32
- package/dist/authoring-references-CmwLc_C8.mjs.map +1 -0
- package/dist/capabilities-D55dq-fO.mjs +582 -0
- package/dist/capabilities-D55dq-fO.mjs.map +1 -0
- package/dist/capabilities-default.d.mts +5 -12
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +2 -411
- package/dist/capabilities-z95e1mti.d.mts +117 -0
- package/dist/capabilities-z95e1mti.d.mts.map +1 -0
- package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
- package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
- package/dist/{constants-Be91Gay9.d.mts → constants-HL_klgJl.d.mts} +7 -28
- package/dist/constants-HL_klgJl.d.mts.map +1 -0
- package/dist/{controller-proxy-BH05e-C2.mjs → controller-proxy-BQ1YsVxJ.mjs} +12 -21
- package/dist/controller-proxy-BQ1YsVxJ.mjs.map +1 -0
- package/dist/controller-proxy-Cmzdr9tV.d.mts +65 -0
- package/dist/controller-proxy-Cmzdr9tV.d.mts.map +1 -0
- package/dist/default-D22AKTqE.mjs +233 -0
- package/dist/default-D22AKTqE.mjs.map +1 -0
- package/dist/{digest-SJmoYVaK.d.mts → digest-FnTTDBq2.d.mts} +2 -3
- package/dist/digest-FnTTDBq2.d.mts.map +1 -0
- package/dist/digest.d.mts +1 -1
- package/dist/digest.mjs.map +1 -1
- package/dist/engine-D950TfvC.mjs +3098 -0
- package/dist/engine-D950TfvC.mjs.map +1 -0
- package/dist/engine.d.mts +4 -3
- package/dist/engine.mjs +3 -3
- package/dist/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
- package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
- package/dist/{index-l_EK4yK7.d.mts → index-B8TLBd1G.d.mts} +18 -72
- package/dist/index-B8TLBd1G.d.mts.map +1 -0
- package/dist/index-CaX3hZmr.d.mts +23813 -0
- package/dist/index-CaX3hZmr.d.mts.map +1 -0
- package/dist/{index-x0qprHgK.d.mts → index-CeUlGkoU.d.mts} +58 -28
- package/dist/index-CeUlGkoU.d.mts.map +1 -0
- package/dist/{index-nhrgbgO5.d.mts → index-DYHdvSE8.d.mts} +347 -88
- package/dist/index-DYHdvSE8.d.mts.map +1 -0
- package/dist/lifecycle-DFzxVxpq.d.mts +54346 -0
- package/dist/lifecycle-DFzxVxpq.d.mts.map +1 -0
- package/dist/{memory-thread-repository-KWHQWFEW.mjs → memory-thread-repository-DmLadygY.mjs} +60 -30
- package/dist/memory-thread-repository-DmLadygY.mjs.map +1 -0
- package/dist/protocol-BQ7uIDYT.mjs +569 -0
- package/dist/protocol-BQ7uIDYT.mjs.map +1 -0
- package/dist/protocol.d.mts +4 -4
- package/dist/protocol.mjs +4 -3
- package/dist/{react-B3ybjJ-X.mjs → react-DybJ5pWw.mjs} +159 -46
- package/dist/react-DybJ5pWw.mjs.map +1 -0
- package/dist/react-integration.d.mts +2 -2
- package/dist/react-integration.mjs +2 -2
- package/dist/react.d.mts +3 -3
- package/dist/react.mjs +3 -3
- package/dist/references/assistant-operations.md +224 -180
- package/dist/references/core-rules.md +6 -3
- package/dist/references/element-types.md +189 -0
- package/dist/references/expressions.md +307 -168
- package/dist/references/fair-by-design.md +9 -9
- package/dist/references/follow-ups.md +151 -0
- package/dist/references/localization.md +26 -12
- package/dist/references/response-variables.md +38 -0
- package/dist/references/rich-text-content.md +22 -43
- package/dist/references/source-material-surveys.md +20 -24
- package/dist/references/survey-data-model.md +36 -102
- package/dist/{request-context-CG4SWijt.mjs → request-context-KVh8VjgH.mjs} +4 -10
- package/dist/request-context-KVh8VjgH.mjs.map +1 -0
- package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
- package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
- package/dist/server-agent.d.mts +62 -43
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +73 -40
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +24 -42
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +12 -12
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +2 -2
- package/dist/server-tasks.mjs +2 -313
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +14 -36
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +5 -5
- package/dist/server.mjs.map +1 -1
- package/dist/storage-postgres.d.mts +9 -25
- package/dist/storage-postgres.d.mts.map +1 -1
- package/dist/storage-postgres.mjs +5 -4
- package/dist/storage-postgres.mjs.map +1 -1
- package/dist/tasks-Coh5N027.mjs +397 -0
- package/dist/tasks-Coh5N027.mjs.map +1 -0
- package/dist/{tasks-CYqrcaRO.d.mts → tasks-wb9M-bak.d.mts} +25 -41
- package/dist/tasks-wb9M-bak.d.mts.map +1 -0
- package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
- package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
- package/dist/{thread-handlers-DBAaU7BA.d.mts → thread-handlers-C3Iya92v.d.mts} +9 -40
- package/dist/thread-handlers-C3Iya92v.d.mts.map +1 -0
- package/dist/{tools-BvSovehT.mjs → tools-D0KBDolR.mjs} +677 -459
- package/dist/tools-D0KBDolR.mjs.map +1 -0
- package/dist/turn-survey-draft-B34lggSG.d.mts +185 -0
- package/dist/turn-survey-draft-B34lggSG.d.mts.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.mts +15 -54
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +203 -156
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +11 -1
- package/package.json +41 -36
- package/dist/authoring-references-BJQ_1nMX.mjs.map +0 -1
- package/dist/capabilities-1KNnNO1_.mjs +0 -73
- package/dist/capabilities-1KNnNO1_.mjs.map +0 -1
- package/dist/capabilities-DVTfogjv.d.mts +0 -193
- package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
- package/dist/capabilities-default.mjs.map +0 -1
- package/dist/constants-Be91Gay9.d.mts.map +0 -1
- package/dist/controller-proxy-BH05e-C2.mjs.map +0 -1
- package/dist/controller-proxy-Bd0ZHBPF.d.mts +0 -85
- package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +0 -1
- package/dist/digest-SJmoYVaK.d.mts.map +0 -1
- package/dist/engine-Bo85Oyj0.mjs +0 -6723
- package/dist/engine-Bo85Oyj0.mjs.map +0 -1
- package/dist/index-Tnqv-yKW.d.mts +0 -777
- package/dist/index-Tnqv-yKW.d.mts.map +0 -1
- package/dist/index-l_EK4yK7.d.mts.map +0 -1
- package/dist/index-nhrgbgO5.d.mts.map +0 -1
- package/dist/index-x0qprHgK.d.mts.map +0 -1
- package/dist/lifecycle-Bx0aq_4S.d.mts +0 -529
- package/dist/lifecycle-Bx0aq_4S.d.mts.map +0 -1
- package/dist/memory-thread-repository-KWHQWFEW.mjs.map +0 -1
- package/dist/protocol-BhzQbx81.mjs +0 -1173
- package/dist/protocol-BhzQbx81.mjs.map +0 -1
- package/dist/react-B3ybjJ-X.mjs.map +0 -1
- package/dist/references/embedded-forms.md +0 -126
- package/dist/references/item-types.md +0 -407
- package/dist/request-context-CG4SWijt.mjs.map +0 -1
- package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
- package/dist/server-tasks.mjs.map +0 -1
- package/dist/tasks-CYqrcaRO.d.mts.map +0 -1
- package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
- package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
- package/dist/thread-handlers-DBAaU7BA.d.mts.map +0 -1
- package/dist/tools-BvSovehT.mjs.map +0 -1
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# Choice Option Embedded Forms
|
|
2
|
-
|
|
3
|
-
Use this reference when a choice option needs an inline input such as “Other, please specify”,
|
|
4
|
-
“Yes, explain”, or an option-specific text, number, date, or select field.
|
|
5
|
-
|
|
6
|
-
## Tool And Transaction Choice
|
|
7
|
-
|
|
8
|
-
- There is no `allowOther` flag. The option config field is `embeddedForm`.
|
|
9
|
-
- For a newly created choice item, put the option and its `embeddedForm` directly in the same
|
|
10
|
-
`create-item` operation, with every required translation in `create-item.translations`.
|
|
11
|
-
- For an existing option, inspect its exact item and option ids, then use `set-embedded-form`.
|
|
12
|
-
Put the complete form structure and all visible content in that operation's `translations`.
|
|
13
|
-
The `translations` property belongs inside the `set-embedded-form` operation object, never beside
|
|
14
|
-
the `survey_change` `operations` array.
|
|
15
|
-
- To add the option and form together, use one `survey_change` proposal containing
|
|
16
|
-
`add-choice-option` with an explicit stable id, immediately followed by `set-embedded-form`
|
|
17
|
-
targeting that id.
|
|
18
|
-
- Use `survey-json-patch` only for editable behavior that these typed operations cannot represent.
|
|
19
|
-
|
|
20
|
-
## Exact Config Shape
|
|
21
|
-
|
|
22
|
-
The config key is singular `embeddedForm`, but translation content-key prefixes are plural
|
|
23
|
-
`embeddedForms.<optionId>`. Do not interchange them.
|
|
24
|
-
|
|
25
|
-
`embeddedForm` is a flat object containing `id`, `authoringMode`, and `fieldGroups`. Fields belong inside each field
|
|
26
|
-
group; there is no `itemType`, `config`, or top-level `fields` wrapper.
|
|
27
|
-
|
|
28
|
-
Default to `authoringMode: "simple"` for an inline follow-up with one supported field in one
|
|
29
|
-
ordinary single-column group. Use `authoringMode: "custom"` only for a blank, multi-field,
|
|
30
|
-
multi-group, conditional, or layout-customized follow-up. Any structural change is a one-way
|
|
31
|
-
transition to custom; retain existing fields and translations.
|
|
32
|
-
|
|
33
|
-
`fieldGroups[].layout` values are responsive column counts, not field spans. Use `1` at every
|
|
34
|
-
breakpoint for one full-width field. Supported counts are `1` through `4`.
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"id": "other",
|
|
39
|
-
"key": "other",
|
|
40
|
-
"embeddedForm": {
|
|
41
|
-
"id": "other",
|
|
42
|
-
"authoringMode": "simple",
|
|
43
|
-
"fieldGroups": [
|
|
44
|
-
{
|
|
45
|
-
"id": "other_details",
|
|
46
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
47
|
-
"fields": [
|
|
48
|
-
{
|
|
49
|
-
"id": "other_text",
|
|
50
|
-
"key": "other_text",
|
|
51
|
-
"type": "input",
|
|
52
|
-
"required": true,
|
|
53
|
-
"controller": { "type": "input", "inputMode": "text" },
|
|
54
|
-
"validations": []
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
For comparison, use `custom` when the follow-up really needs multiple controls. Keep its fields
|
|
64
|
-
and their translations together; do not use this shape for a single text field just because it is
|
|
65
|
-
inside a choice option.
|
|
66
|
-
|
|
67
|
-
```json
|
|
68
|
-
{
|
|
69
|
-
"id": "contact_details",
|
|
70
|
-
"authoringMode": "custom",
|
|
71
|
-
"fieldGroups": [
|
|
72
|
-
{
|
|
73
|
-
"id": "details",
|
|
74
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
75
|
-
"fields": [
|
|
76
|
-
{
|
|
77
|
-
"id": "contact_name",
|
|
78
|
-
"key": "contact_name",
|
|
79
|
-
"type": "input",
|
|
80
|
-
"controller": { "type": "input", "inputMode": "text" }
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"id": "contact_note",
|
|
84
|
-
"key": "contact_note",
|
|
85
|
-
"type": "textarea",
|
|
86
|
-
"controller": { "type": "textarea", "rows": 3 }
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Use an explicit, stable option id. The config option id, translation prefix, and embedded response
|
|
95
|
-
reference must agree exactly. When editing an existing option, copy its current `id` exactly,
|
|
96
|
-
including case. The `embeddedForms.<optionId>` translation segment always comes from the containing
|
|
97
|
-
choice option's `id`; it does not come from the option `key` or the nested `embeddedForm.id`.
|
|
98
|
-
|
|
99
|
-
## Required Content
|
|
100
|
-
|
|
101
|
-
Every embedded field requires a visible label translation in the same atomic proposal:
|
|
102
|
-
|
|
103
|
-
- option label: `options.other.label`;
|
|
104
|
-
- field label: `embeddedForms.other.fields.other_text.label`;
|
|
105
|
-
- optional placeholder: `embeddedForms.other.fields.other_text.placeholder`;
|
|
106
|
-
- validation message, when configured:
|
|
107
|
-
`embeddedForms.other.fields.other_text.validations.<validationId>.message`;
|
|
108
|
-
- select option label, when applicable:
|
|
109
|
-
`embeddedForms.other.fields.other_text.options.<selectOptionId>.label`.
|
|
110
|
-
|
|
111
|
-
Do not duplicate completion wording in both the option and field labels. For “Other, please
|
|
112
|
-
specify”, use option label “Other” and field label “Please specify”.
|
|
113
|
-
|
|
114
|
-
## Existing-Item Typed Operation Checklist
|
|
115
|
-
|
|
116
|
-
1. Inspect the existing choice item and copy the exact item and option ids.
|
|
117
|
-
2. Call `set-embedded-form` with the complete flat form structure and explicit `authoringMode`.
|
|
118
|
-
3. Use canonical field types such as `input`, with a matching controller; there is no `text` type.
|
|
119
|
-
4. Put relative content keys such as `fields.other_text.label` in the same operation's
|
|
120
|
-
`translations`; the engine adds `embeddedForms.<optionId>.` automatically.
|
|
121
|
-
5. Use one column at every breakpoint when a lone field should fill the form width.
|
|
122
|
-
6. Before adding/reordering groups or fields on a simple follow-up, let the typed mutation promote
|
|
123
|
-
it to custom; do not construct multi-field data with `authoringMode: "simple"`.
|
|
124
|
-
|
|
125
|
-
Embedded response slots use `embedded.<optionId>.<fieldKey>`, for example
|
|
126
|
-
`embedded.other.other_text`.
|
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
# Item Types
|
|
2
|
-
|
|
3
|
-
Use current context to confirm which item types are available in the registry. The default registry includes these deterministically creatable types.
|
|
4
|
-
|
|
5
|
-
## group
|
|
6
|
-
|
|
7
|
-
Structure/container item:
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"itemType": "group",
|
|
12
|
-
"config": { "items": [], "shuffleItems": false }
|
|
13
|
-
}
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Root group additionally has `config.isRoot: true`. Non-root groups should not have `isRoot`.
|
|
17
|
-
Visible group title uses content key `title`.
|
|
18
|
-
|
|
19
|
-
Use a group when its children form a meaningful, independently managed section or require a shared hierarchy. Do not create a group solely to render an isolated heading or a paragraph; use an `infoItem` for explanatory content and a `page-break` when a respondent page boundary is intended. A non-root group must be created empty. In the same validated proposal, create it first and then create each child with that group id in `target.parentItemId`; do not pre-populate `config.items` or use a raw patch for ordinary group creation.
|
|
20
|
-
|
|
21
|
-
## Editor Presentation
|
|
22
|
-
|
|
23
|
-
Every item has three distinct naming/presentation surfaces:
|
|
24
|
-
|
|
25
|
-
- `key`: stable coding key used in expressions and response paths. Change it with `update-item-key`.
|
|
26
|
-
- `metadata.itemLabel`: internal editor label, not shown to respondents. Change it with `update-item-label`.
|
|
27
|
-
- `metadata.editorItemColor`: editor-only color swatch, not shown to respondents. Change it with `update-item-color`.
|
|
28
|
-
|
|
29
|
-
Across editor views, an unqualified request for labels on one or more items/questions means `metadata.itemLabel`, including the visible \"Add label...\" editor control. It does not mean respondent-facing question titles, option labels, or form-field labels unless the user says so. Do not ask for clarification when the requested item set is clear; inspect/search to resolve that set when needed. Clarify only if the request explicitly spans multiple label surfaces.
|
|
30
|
-
|
|
31
|
-
`update-item-color.color` must be one of the editor palette values provided in the `survey_change` tool contract. Use it only when the user asks for editor organization or color; it must not be used to imply respondent-facing meaning.
|
|
32
|
-
|
|
33
|
-
## page-break
|
|
34
|
-
|
|
35
|
-
Page-break/structural item. It has no response slots. Visible title uses content key `title`.
|
|
36
|
-
|
|
37
|
-
## infoItem
|
|
38
|
-
|
|
39
|
-
Non-interactive content item. Config is normally an empty object. Visible body text uses content key `content`.
|
|
40
|
-
For plain text, create or update `content` with typed translation operations.
|
|
41
|
-
For formatted text, load `assistant-operations` with the operation and item type and use only content surfaces marked `format=rich` in its capability profile. Prefer the model-facing `richTextBlocks` shorthand there; it normalizes to canonical richText `content`. A typed field accepting structured `content` does not by itself guarantee formatted rendering. See rich-text-content.md.
|
|
42
|
-
|
|
43
|
-
## choiceItem
|
|
44
|
-
|
|
45
|
-
Interactive choice question:
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
{
|
|
49
|
-
"itemType": "choiceItem",
|
|
50
|
-
"config": {
|
|
51
|
-
"id": "<itemId>",
|
|
52
|
-
"maxSelection": 1,
|
|
53
|
-
"shuffleOptions": false,
|
|
54
|
-
"options": [{ "id": "fuji", "key": "FUJI" }]
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Choice conventions:
|
|
60
|
-
|
|
61
|
-
- `maxSelection: 1` means single select.
|
|
62
|
-
- `maxSelection: null` means multiple select with no cap.
|
|
63
|
-
- `maxSelection: n` means multiple select capped at n selections.
|
|
64
|
-
- If the user says "multiple choice", "multiple answer", "select all that apply", "choose all", or "checkboxes", create a `choiceItem` with `maxSelection: null` unless the user gives a specific cap.
|
|
65
|
-
- If the user says "choose up to N", "select at most N", or "maximum N", create a `choiceItem` with `maxSelection: N`.
|
|
66
|
-
- If the user says "single choice", "choose one", or options are mutually exclusive by wording, create a `choiceItem` with `maxSelection: 1`.
|
|
67
|
-
- `options[].id` is the stable option id.
|
|
68
|
-
- `options[].key` is optional but should be stable and coding-friendly. When you provide it, use lowercase snake-style keys such as `other` or `opt_1`, matching the editor's choice key normalization.
|
|
69
|
-
- `options[].hasDisplayCondition` is an optional boolean used by the editor to mark options that participate in branching/display logic.
|
|
70
|
-
- Option labels are translations at `options.<optionId>.label`.
|
|
71
|
-
- Do not use provider-specific label metadata such as `labelKey`. CASE resolves every respondent-visible option label from `options.<optionId>.label` translations.
|
|
72
|
-
- CASE choice options do not currently have a separate visible option-description field. If source material has an option-specific parenthetical/note, include it in the option label or move it to nearby question content. The `survey_change` normalizer preserves shorthand `option.note`, `option.description`, `option.helpText`, and `option.footnoteMarker` by folding them into the label translation.
|
|
73
|
-
- If an option has an embedded form, do not duplicate trailing completion words in both the option label and field label. The normalizer strips trailing `, namelijk`, `, please specify`, `, specify`, `, explain`, and `, toelichting` from embedded-form option labels before validation.
|
|
74
|
-
- The main response slot is `<itemId>...get...<config.id>`. For a new choice item, always set
|
|
75
|
-
`config.id` to the exact same value as `item.id`; do not invent a second semantic name from the
|
|
76
|
-
question wording and do not add ordering suffixes such as `q1`. The choice response-slot ID has no
|
|
77
|
-
direct editor control and expressions refer to it exactly, so changing it later requires updating
|
|
78
|
-
every reference in the same change.
|
|
79
|
-
- Single select returns `reference`; multiple select returns `reference[]`.
|
|
80
|
-
|
|
81
|
-
Embedded option forms:
|
|
82
|
-
|
|
83
|
-
- There is no `allowOther` flag in the current choice item model. Do not use `allowOther` to create free-text "Other" inputs.
|
|
84
|
-
- CRITICAL — exact key name: the field on the option object is `embeddedForm` (singular, one object). It is NEVER `embeddedForms` (plural) and NEVER an array. The option's recognized keys are only: `id`, `key`, `label`, `hasDisplayCondition`, `embeddedFormItemId`, `embeddedForm`. Any other key (including a typo'd/pluralized `embeddedForms`) is silently dropped during validation — no error is raised, the field simply never appears, and you must not report the change as successful if you are not certain you used the exact singular key.
|
|
85
|
-
- CRITICAL — exact shape: `embeddedForm` fields are never listed directly as `embeddedForm.fields`. They must be nested inside `embeddedForm.fieldGroups`, an array of groups, each with its own `fields` array: `{ "id": "...", "fieldGroups": [ { "id": "...", "fields": [ { "id": "...", "type": "input", "controller": { "type": "input", ... } } ] } ] }`. A top-level `fields` array with no `fieldGroups` wrapper will not render anything.
|
|
86
|
-
- CRITICAL — `embeddedForm` is a FLAT object with `id`, `authoringMode`, and `fieldGroups`. Do NOT invent an envelope like `{ "itemType": "formItem", "config": { "fields": [...] } }`. There is no `itemType`/`config` wrapper anywhere inside `embeddedForm`; `fieldGroups` sits directly on the `embeddedForm` object itself.
|
|
87
|
-
- CRITICAL — always set an explicit `id` on any option that will have an `embeddedForm` (e.g. `"id": "other"`, lowercase, coding-friendly). If you omit `id`, one is auto-generated from `label` and PRESERVES THE EXACT CASE of the label text (e.g. label "Other" auto-generates id "Other", not "other") — you cannot reliably predict it, and every embedded-form translation content key (`embeddedForms.<optionId>...`) and response slot (`embedded.<optionId>...`) must match that exact generated id, including case. Setting an explicit lowercase `id` yourself removes this guesswork entirely.
|
|
88
|
-
- To attach input controls to a choice option, put an `embeddedForm` on that option. It uses the same `FormItemConfig` shape as a `formItem` (`id` + `authoringMode` + `fieldGroups`), but it is embedded directly on the option object — it is never a separate `formItem` sibling and never uses `embeddedFormItemId` unless you are intentionally referencing a shared/reusable form item defined elsewhere (rare; prefer inline `embeddedForm`).
|
|
89
|
-
- Use `authoringMode: "simple"` only for one internal group containing exactly one field. Use `custom` for a blank or multi-field follow-up. Adding, removing, moving, retyping, or rekeying fields, or changing groups/layouts, is a one-way transition to `custom`; preserve the existing field and translations during that transition.
|
|
90
|
-
- The embedded form renders only while that option is selected. When the option is deselected, existing embedded responses are cleared after user confirmation.
|
|
91
|
-
- Embedded form translation keys are prefixed with `embeddedForms.<optionId>`; for example `embeddedForms.other.fields.other_text.label`. Note this translation content-key prefix IS plural (`embeddedForms`) even though the config field on the option is singular (`embeddedForm`) — these are two different things; do not confuse them.
|
|
92
|
-
- Embedded form response slots are `embedded.<optionId>.<fieldKey>`; for example `embedded.other.other_text`.
|
|
93
|
-
- Use `create-item` with embedded form config and matching translations when creating a new choice item. For an existing choice option, inspect its exact item and option ids and use `set-embedded-form`; its relative form translations are prefixed automatically. If the option is also new, put `add-choice-option` with an explicit id immediately before `set-embedded-form` in the same proposal.
|
|
94
|
-
- MANDATORY: every embedded form field needs a matching `embeddedForms.<optionId>.fields.<fieldId>.label` translation entry when it is introduced. There is no fallback/derived label text — a field without this translation renders its raw key to respondents.
|
|
95
|
-
|
|
96
|
-
Example "Other, please specify" option:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"id": "other",
|
|
101
|
-
"key": "other",
|
|
102
|
-
"embeddedForm": {
|
|
103
|
-
"id": "other",
|
|
104
|
-
"authoringMode": "simple",
|
|
105
|
-
"fieldGroups": [
|
|
106
|
-
{
|
|
107
|
-
"id": "other_details",
|
|
108
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
109
|
-
"fields": [
|
|
110
|
-
{
|
|
111
|
-
"id": "other_text",
|
|
112
|
-
"key": "other_text",
|
|
113
|
-
"type": "input",
|
|
114
|
-
"required": true,
|
|
115
|
-
"controller": { "type": "input", "inputMode": "text" },
|
|
116
|
-
"validations": []
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Matching translation keys on the choice item:
|
|
126
|
-
|
|
127
|
-
- `options.other.label`: "Other"
|
|
128
|
-
- `embeddedForms.other.fields.other_text.label`: "Please specify"
|
|
129
|
-
- Optional placeholder: `embeddedForms.other.fields.other_text.placeholder`
|
|
130
|
-
- Additional embedded field content keys follow the same form-field families as a top-level form item, for example:
|
|
131
|
-
- `embeddedForms.other.fields.other_text.description`
|
|
132
|
-
- `embeddedForms.other.fields.other_text.error`
|
|
133
|
-
- `embeddedForms.other.fields.other_text.unit`
|
|
134
|
-
- `embeddedForms.other.fields.other_text.checkedLabel`
|
|
135
|
-
- `embeddedForms.other.fields.other_text.uncheckedLabel`
|
|
136
|
-
- `embeddedForms.other.fields.other_text.options.<optionId>.label`
|
|
137
|
-
- `embeddedForms.other.fields.other_text.validations.<validationId>.message`
|
|
138
|
-
- `embeddedForms.other.fieldGroups.<groupId>.title`
|
|
139
|
-
- `embeddedForms.other.fieldGroups.<groupId>.description`
|
|
140
|
-
|
|
141
|
-
### Full worked example: create a choiceItem with an embedded "Other" text field
|
|
142
|
-
|
|
143
|
-
One `create-item` operation carries the whole item plus every translation it needs — the option labels AND the embedded form field label:
|
|
144
|
-
|
|
145
|
-
```json
|
|
146
|
-
{
|
|
147
|
-
"kind": "create-item",
|
|
148
|
-
"target": { "parentItemId": "<root-group-id-or-key>", "index": 1 },
|
|
149
|
-
"item": {
|
|
150
|
-
"id": "choice_2",
|
|
151
|
-
"key": "choice_2",
|
|
152
|
-
"itemType": "choiceItem",
|
|
153
|
-
"config": {
|
|
154
|
-
"id": "choice_2",
|
|
155
|
-
"maxSelection": 1,
|
|
156
|
-
"shuffleOptions": false,
|
|
157
|
-
"options": [
|
|
158
|
-
{ "id": "strawberry", "key": "strawberry" },
|
|
159
|
-
{ "id": "blueberry", "key": "blueberry" },
|
|
160
|
-
{
|
|
161
|
-
"id": "other",
|
|
162
|
-
"key": "other",
|
|
163
|
-
"embeddedForm": {
|
|
164
|
-
"id": "other",
|
|
165
|
-
"authoringMode": "simple",
|
|
166
|
-
"fieldGroups": [
|
|
167
|
-
{
|
|
168
|
-
"id": "other_details",
|
|
169
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
170
|
-
"fields": [
|
|
171
|
-
{
|
|
172
|
-
"id": "other_text",
|
|
173
|
-
"key": "other_text",
|
|
174
|
-
"type": "input",
|
|
175
|
-
"required": true,
|
|
176
|
-
"controller": { "type": "input", "inputMode": "text" },
|
|
177
|
-
"validations": []
|
|
178
|
-
}
|
|
179
|
-
]
|
|
180
|
-
}
|
|
181
|
-
]
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
"translations": [
|
|
188
|
-
{ "locale": "en", "contentKey": "title", "plainText": "Favorite berry" },
|
|
189
|
-
{ "locale": "en", "contentKey": "options.strawberry.label", "plainText": "Strawberry" },
|
|
190
|
-
{ "locale": "en", "contentKey": "options.blueberry.label", "plainText": "Blueberry" },
|
|
191
|
-
{ "locale": "en", "contentKey": "options.other.label", "plainText": "Other" },
|
|
192
|
-
{
|
|
193
|
-
"locale": "en",
|
|
194
|
-
"contentKey": "embeddedForms.other.fields.other_text.label",
|
|
195
|
-
"plainText": "Please specify"
|
|
196
|
-
}
|
|
197
|
-
]
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Notice `config.id` equals the item id, every option has a label translation, and the embedded field has its own label translation under the `embeddedForms.other.*` prefix — omitting any one of these leaves that piece of UI blank.
|
|
202
|
-
|
|
203
|
-
### Full worked example: create a multiple choice question
|
|
204
|
-
|
|
205
|
-
Use `maxSelection: null` for "select all that apply" / checkbox-style questions:
|
|
206
|
-
|
|
207
|
-
```json
|
|
208
|
-
{
|
|
209
|
-
"kind": "create-item",
|
|
210
|
-
"target": { "parentItemId": "<root-group-id-or-key>", "index": 1 },
|
|
211
|
-
"item": {
|
|
212
|
-
"id": "industries_worked",
|
|
213
|
-
"key": "industries_worked",
|
|
214
|
-
"itemType": "choiceItem",
|
|
215
|
-
"config": {
|
|
216
|
-
"id": "industries_worked",
|
|
217
|
-
"maxSelection": null,
|
|
218
|
-
"shuffleOptions": false,
|
|
219
|
-
"options": [
|
|
220
|
-
{ "id": "healthcare", "key": "HEALTHCARE" },
|
|
221
|
-
{ "id": "education", "key": "EDUCATION" },
|
|
222
|
-
{ "id": "food_service", "key": "FOOD_SERVICE" }
|
|
223
|
-
]
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
"translations": [
|
|
227
|
-
{
|
|
228
|
-
"locale": "en",
|
|
229
|
-
"contentKey": "title",
|
|
230
|
-
"plainText": "Which industries have you worked in? Select all that apply."
|
|
231
|
-
},
|
|
232
|
-
{ "locale": "en", "contentKey": "options.healthcare.label", "plainText": "Healthcare" },
|
|
233
|
-
{ "locale": "en", "contentKey": "options.education.label", "plainText": "Education" },
|
|
234
|
-
{ "locale": "en", "contentKey": "options.food_service.label", "plainText": "Food service" }
|
|
235
|
-
]
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## formItem
|
|
240
|
-
|
|
241
|
-
Interactive form question:
|
|
242
|
-
|
|
243
|
-
```json
|
|
244
|
-
{
|
|
245
|
-
"itemType": "formItem",
|
|
246
|
-
"config": {
|
|
247
|
-
"id": "<itemId>",
|
|
248
|
-
"authoringMode": "custom",
|
|
249
|
-
"fieldGroups": [
|
|
250
|
-
{
|
|
251
|
-
"id": "main",
|
|
252
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
253
|
-
"fields": []
|
|
254
|
-
}
|
|
255
|
-
]
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
The item's config has `id`, `authoringMode`, and `fieldGroups`. `fieldGroups` sits directly on config — never nest it under another wrapper, and never put a top-level `fields` array directly on config.
|
|
261
|
-
|
|
262
|
-
Authoring modes and Add item presets:
|
|
263
|
-
|
|
264
|
-
- `Text` → `input`, `Long text` → `textarea`, `Number` → `number`, `Slider` → `slider`, `Dropdown` → `select`, `Date` → `datepicker`, and `Switch` → `switch`.
|
|
265
|
-
- These seven presets use `authoringMode: "simple"` with exactly one internal field group containing exactly one field. The field group remains part of persisted/runtime data; hiding it is only an editor abstraction.
|
|
266
|
-
- `Blank form` uses `authoringMode: "custom"`. Any finished multi-field/group form also uses custom mode.
|
|
267
|
-
- Simple mode exposes the existing field's label, description, required state, and compatible controller settings. It does not expose groups, layouts, field reordering, field keys, or field-type switching.
|
|
268
|
-
- Adding/removing/moving fields or groups, changing layout, or changing the sole field's key/type is a one-way transition to custom. Preserve the existing field and every translation. Never leave a multi-field, empty, or multi-group structure marked simple.
|
|
269
|
-
- If a stored simple form lacks its sole first field, treat it as custom without deleting any remaining data.
|
|
270
|
-
|
|
271
|
-
Fields normally need at least one non-empty group to be useful. An explicit `authoringMode: "custom"` with one empty group is the supported Blank form starting preset.
|
|
272
|
-
|
|
273
|
-
Field group config:
|
|
274
|
-
|
|
275
|
-
- `id`: stable group id.
|
|
276
|
-
- `hasDisplayCondition`: optional boolean used by the editor to mark groups that participate in branching/display logic.
|
|
277
|
-
- `layout`: column counts by container breakpoint, using integers 1 through 4. These are grid column counts, not field spans; use 1 at every breakpoint for a lone full-width field.
|
|
278
|
-
|
|
279
|
-
Example field group layout:
|
|
280
|
-
|
|
281
|
-
```json
|
|
282
|
-
{
|
|
283
|
-
"id": "main",
|
|
284
|
-
"hasDisplayCondition": true,
|
|
285
|
-
"layout": { "base": 1, "xs": 1, "sm": 2, "md": 2, "xl": 3 },
|
|
286
|
-
"fields": []
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Field group translations:
|
|
291
|
-
|
|
292
|
-
- `fieldGroups.<groupId>.title`
|
|
293
|
-
- `fieldGroups.<groupId>.description`
|
|
294
|
-
|
|
295
|
-
Field shape:
|
|
296
|
-
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"id": "age",
|
|
300
|
-
"key": "AGE",
|
|
301
|
-
"type": "number",
|
|
302
|
-
"required": true,
|
|
303
|
-
"controller": { "type": "number", "step": 1 },
|
|
304
|
-
"validations": []
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
`key` is the field's exact response-slot identifier. Choose a stable coding key for the field's
|
|
309
|
-
role within this form item. Do not copy the question wording, duplicate context already present in
|
|
310
|
-
the item key, or add ordering suffixes such as `q1`. For a single-field form, concise keys such as
|
|
311
|
-
`response`, `selection`, `value`, or `other_text` are often appropriate. Keys only need to be unique
|
|
312
|
-
within the form item, so the same general key can be reused in another item. A user can edit the
|
|
313
|
-
field key in the form editor, but changing it also changes the response-slot identifier; any
|
|
314
|
-
expressions that use the old slot must be updated in the same change.
|
|
315
|
-
|
|
316
|
-
Field types and default controllers:
|
|
317
|
-
|
|
318
|
-
- `input`: `{ "type": "input", "inputMode": "text" }`
|
|
319
|
-
- `textarea`: `{ "type": "textarea", "rows": 3 }`
|
|
320
|
-
- `number`: `{ "type": "number", "step": 1 }`
|
|
321
|
-
- `datepicker`: `{ "type": "datepicker", "mode": "year-month-day" }`
|
|
322
|
-
- `switch`: `{ "type": "switch", "defaultChecked": false }`
|
|
323
|
-
- `select`: `{ "type": "select", "options": [] }`
|
|
324
|
-
- `slider`: `{ "type": "slider", "min": 0, "max": 100, "step": 1, "defaultValue": 50, "showValue": true }`
|
|
325
|
-
|
|
326
|
-
Supported controller properties by type:
|
|
327
|
-
|
|
328
|
-
- `input`: `placeholder`, `inputMode`, `autocomplete`
|
|
329
|
-
- `textarea`: `placeholder`, `rows`
|
|
330
|
-
- `number`: `placeholder`, `step`, `unit`, `defaultValue`
|
|
331
|
-
- `datepicker`: `placeholder`, `mode` where mode is `year`, `year-month`, or `year-month-day`
|
|
332
|
-
- `switch`: `checkedLabel`, `uncheckedLabel`, `defaultChecked`
|
|
333
|
-
- `select`: `placeholder`, `allowClear`, `options`
|
|
334
|
-
- `slider`: `min`, `max`, `step`, `defaultValue`, `showValue`
|
|
335
|
-
|
|
336
|
-
Select controller option shape:
|
|
337
|
-
|
|
338
|
-
```json
|
|
339
|
-
{ "id": "weekly", "value": "weekly" }
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Visible select-option labels do NOT live in the controller config. They live in translations at `fields.<fieldId>.options.<optionId>.label`.
|
|
343
|
-
If a select controller option has `id: "minder_7_dagen", value: "minder_7_dagen"`, the visible Dutch label still needs a separate translation such as `fields.<fieldId>.options.minder_7_dagen.label`: "minder dan 7 dagen". Without this translation, respondents see the raw key/value in the dropdown.
|
|
344
|
-
|
|
345
|
-
Field translation keys:
|
|
346
|
-
|
|
347
|
-
- `fields.<fieldId>.label`
|
|
348
|
-
- `fields.<fieldId>.description`
|
|
349
|
-
- `fields.<fieldId>.error`
|
|
350
|
-
- `fields.<fieldId>.placeholder`
|
|
351
|
-
- `fields.<fieldId>.unit`
|
|
352
|
-
- `fields.<fieldId>.checkedLabel`
|
|
353
|
-
- `fields.<fieldId>.uncheckedLabel`
|
|
354
|
-
- `fields.<fieldId>.options.<optionId>.label`
|
|
355
|
-
- `fields.<fieldId>.validations.<validationId>.message`
|
|
356
|
-
|
|
357
|
-
Validation configs use `{ "id", "type", "value"?, "message"? }`.
|
|
358
|
-
Supported validation types: `required`, `minLength`, `maxLength`, `pattern`, `email`, `url`, `integer`, `min`, `max`, `minDate`, `maxDate`, `mustBeTrue`.
|
|
359
|
-
|
|
360
|
-
MANDATORY: every field needs a matching `fields.<fieldId>.label` translation entry in the SAME operation that creates it. There is no fallback/derived label text — a field without this translation renders with no visible label (only its raw type, e.g. "Input") to respondents. Before returning a create-item or raw-patch result, check that you added exactly one label translation per field you defined.
|
|
361
|
-
MANDATORY: every `select` option needs a matching `fields.<fieldId>.options.<optionId>.label` translation entry in the SAME operation that creates it. Do not rely on `controller.options[].label`; generated survey JSON strips option labels from config and restores them from translations. Missing select option translations render raw values such as `minder_7_dagen`.
|
|
362
|
-
|
|
363
|
-
### Full worked example: create a formItem with two fields
|
|
364
|
-
|
|
365
|
-
```json
|
|
366
|
-
{
|
|
367
|
-
"kind": "create-item",
|
|
368
|
-
"target": { "parentItemId": "<root-group-id-or-key>", "index": 1 },
|
|
369
|
-
"item": {
|
|
370
|
-
"id": "form_1",
|
|
371
|
-
"key": "form_1",
|
|
372
|
-
"itemType": "formItem",
|
|
373
|
-
"config": {
|
|
374
|
-
"id": "form_1",
|
|
375
|
-
"authoringMode": "custom",
|
|
376
|
-
"fieldGroups": [
|
|
377
|
-
{
|
|
378
|
-
"id": "main",
|
|
379
|
-
"layout": { "base": 1, "xs": 1, "sm": 1, "md": 1, "xl": 1 },
|
|
380
|
-
"fields": [
|
|
381
|
-
{
|
|
382
|
-
"id": "age",
|
|
383
|
-
"key": "AGE",
|
|
384
|
-
"type": "number",
|
|
385
|
-
"required": true,
|
|
386
|
-
"controller": { "type": "number", "step": 1 },
|
|
387
|
-
"validations": []
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
"id": "comments",
|
|
391
|
-
"key": "COMMENTS",
|
|
392
|
-
"type": "textarea",
|
|
393
|
-
"controller": { "type": "textarea", "rows": 3 },
|
|
394
|
-
"validations": []
|
|
395
|
-
}
|
|
396
|
-
]
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
}
|
|
400
|
-
},
|
|
401
|
-
"translations": [
|
|
402
|
-
{ "locale": "en", "contentKey": "title", "plainText": "About you" },
|
|
403
|
-
{ "locale": "en", "contentKey": "fields.age.label", "plainText": "Your age" },
|
|
404
|
-
{ "locale": "en", "contentKey": "fields.comments.label", "plainText": "Any comments?" }
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"request-context-CG4SWijt.mjs","names":[],"sources":["../src/server/request-context.ts"],"sourcesContent":["import {\n MASTRA_RESOURCE_ID_KEY,\n MASTRA_THREAD_ID_KEY,\n RequestContext,\n} from \"@mastra/core/request-context\";\nimport type { OpenAICompatibleConfig } from \"@mastra/core/llm\";\nimport type { AgentExecutionOptions } from \"@mastra/core/agent\";\nimport {\n surveyAssistantSnapshotPayloadSchema,\n surveyAssistantApplyOutcomeSchema,\n surveyDocumentVersionSchema,\n type SurveyAssistantApplyOutcome,\n type SurveyDocumentSnapshot,\n type SurveyDocumentVersion,\n} from \"../protocol\";\nimport {\n createSurveyAssistantEngineContext,\n TurnSurveyDraft,\n type SurveyAssistantCapabilitySet,\n type SurveyAssistantEngineContext,\n} from \"../engine\";\nimport { defaultSurveyAssistantCapabilitySet } from \"../capabilities/default\";\nimport type {\n SurveyAssistantAttachmentAccess,\n SurveyAssistantModelAttachment,\n} from \"./thread-documents\";\n\nexport interface SurveyAssistantInvocationContext {\n kind: \"chat\";\n}\n\nexport interface SurveyAssistantIdentityContext {\n authority: \"experimental-client-request\" | \"authenticated-host\";\n resourceId: string;\n surveyId?: string;\n threadId: string;\n}\n\nexport interface SurveyAssistantModelSelection {\n id: string;\n model: string | OpenAICompatibleConfig;\n providerName?: string;\n providerOptions?: AgentExecutionOptions[\"providerOptions\"];\n}\n\nexport type SurveyAssistantDocumentTransition =\n | { status: \"no-baseline\" }\n | {\n status: \"session-changed\";\n previousHistoryIndex: number;\n currentHistoryIndex: number;\n }\n | {\n status: \"unchanged\";\n previousHistoryIndex: number;\n currentHistoryIndex: number;\n capabilitiesChanged: boolean;\n }\n | {\n status: \"changed\";\n previousHistoryIndex: number;\n currentHistoryIndex: number;\n historyDirection: \"backward\" | \"forward\" | \"same-index\";\n recordedSources: Array<\"user\" | \"system\" | \"assistant\">;\n capabilitiesChanged: boolean;\n };\n\nexport interface SurveyAssistantRequestContext {\n invocation: SurveyAssistantInvocationContext;\n turnId: string;\n turnDraft: TurnSurveyDraft;\n identity: SurveyAssistantIdentityContext;\n authenticatedResourceId?: string;\n authorizedThreadId?: string;\n applyPolicy?: \"auto\" | \"review\" | \"deny\";\n mastra__resourceId?: string;\n mastra__threadId?: string;\n documentTransition?: SurveyAssistantDocumentTransition;\n engineContext?: SurveyAssistantEngineContext;\n documentSnapshot?: SurveyDocumentSnapshot;\n modelAttachments?: SurveyAssistantModelAttachment[];\n attachmentAccess?: SurveyAssistantAttachmentAccess;\n modelSelection?: SurveyAssistantModelSelection;\n previousApplyOutcome?: SurveyAssistantApplyOutcome;\n}\n\nexport const parseSurveyAssistantSnapshot = (value: unknown): SurveyDocumentSnapshot | null => {\n if (value === undefined || value === null) {\n return null;\n }\n\n return surveyAssistantSnapshotPayloadSchema.parse(value);\n};\n\nexport const parseSurveyAssistantPreviousSettledVersion = (\n value: unknown,\n): SurveyDocumentVersion | null => {\n if (value === undefined || value === null) {\n return null;\n }\n\n return surveyDocumentVersionSchema.parse(value);\n};\n\nexport const parseSurveyAssistantPreviousApplyOutcome = (\n value: unknown,\n): SurveyAssistantApplyOutcome | null => {\n if (value === undefined || value === null) {\n return null;\n }\n\n return surveyAssistantApplyOutcomeSchema.parse(value);\n};\n\nexport const deriveSurveyAssistantDocumentTransition = ({\n current,\n previous,\n}: {\n current: SurveyDocumentSnapshot | null;\n previous: SurveyDocumentVersion | null;\n}): SurveyAssistantDocumentTransition | undefined => {\n if (!current) return undefined;\n if (!previous) return { status: \"no-baseline\" };\n\n const previousHistoryIndex = previous.historyIndex;\n const currentHistoryIndex = current.version.historyIndex;\n if (previous.sessionId !== current.version.sessionId) {\n return {\n status: \"session-changed\",\n previousHistoryIndex,\n currentHistoryIndex,\n };\n }\n\n const capabilitiesChanged = previous.capabilityDigest !== current.version.capabilityDigest;\n if (previous.contentHash === current.version.contentHash) {\n return {\n status: \"unchanged\",\n previousHistoryIndex,\n currentHistoryIndex,\n capabilitiesChanged,\n };\n }\n\n const historyDirection =\n currentHistoryIndex > previousHistoryIndex\n ? \"forward\"\n : currentHistoryIndex < previousHistoryIndex\n ? \"backward\"\n : \"same-index\";\n const recordedSources =\n historyDirection === \"forward\"\n ? [\n ...new Set(\n (current.ui.history?.recent ?? [])\n .filter(\n (entry) => entry.index > previousHistoryIndex && entry.index <= currentHistoryIndex,\n )\n .flatMap((entry) => (entry.source ? [entry.source] : [])),\n ),\n ]\n : [];\n\n return {\n status: \"changed\",\n previousHistoryIndex,\n currentHistoryIndex,\n historyDirection,\n recordedSources,\n capabilitiesChanged,\n };\n};\n\nexport const buildSurveyAssistantRequestContext = ({\n turnDraft,\n identity,\n snapshot,\n previousSettledVersion = null,\n trustedCapabilities = defaultSurveyAssistantCapabilitySet,\n attachmentAccess,\n modelAttachments = [],\n modelSelection,\n previousApplyOutcome,\n}: {\n turnDraft: TurnSurveyDraft;\n identity: SurveyAssistantIdentityContext;\n snapshot: SurveyDocumentSnapshot | null;\n previousSettledVersion?: SurveyDocumentVersion | null;\n trustedCapabilities?: SurveyAssistantCapabilitySet;\n attachmentAccess?: SurveyAssistantAttachmentAccess;\n modelAttachments?: SurveyAssistantModelAttachment[];\n modelSelection?: SurveyAssistantModelSelection;\n previousApplyOutcome?: SurveyAssistantApplyOutcome | null;\n}): RequestContext<SurveyAssistantRequestContext> => {\n const requestContext = new RequestContext<SurveyAssistantRequestContext>();\n\n requestContext.set(\"invocation\", { kind: \"chat\" });\n requestContext.set(\"turnId\", turnDraft.turnId);\n requestContext.set(\"turnDraft\", turnDraft);\n requestContext.set(\"identity\", identity);\n if (identity.authority === \"authenticated-host\") {\n requestContext.set(\"authenticatedResourceId\", identity.resourceId);\n requestContext.set(\"authorizedThreadId\", identity.threadId);\n }\n requestContext.set(MASTRA_RESOURCE_ID_KEY, identity.resourceId);\n requestContext.set(MASTRA_THREAD_ID_KEY, identity.threadId);\n const documentTransition = deriveSurveyAssistantDocumentTransition({\n current: snapshot,\n previous: previousSettledVersion,\n });\n if (documentTransition) {\n requestContext.set(\"documentTransition\", documentTransition);\n }\n if (attachmentAccess && attachmentAccess.documents.length > 0) {\n requestContext.set(\"attachmentAccess\", attachmentAccess);\n }\n if (modelAttachments.length > 0) {\n requestContext.set(\"modelAttachments\", modelAttachments);\n }\n if (modelSelection) {\n requestContext.set(\"modelSelection\", modelSelection);\n }\n if (previousApplyOutcome) {\n requestContext.set(\"previousApplyOutcome\", previousApplyOutcome);\n }\n\n if (!snapshot) {\n return requestContext;\n }\n\n const engineContext = createSurveyAssistantEngineContext(snapshot, trustedCapabilities);\n turnDraft.initialize(engineContext);\n requestContext.set(\"documentSnapshot\", snapshot);\n requestContext.set(\"engineContext\", engineContext);\n\n return requestContext;\n};\n\n/**\n * Returns the materialized request-local survey state. The immutable\n * engineContext remains available as the turn baseline, while every tool uses\n * this accessor so successful same-turn mutations are immediately visible.\n */\nexport const getSurveyAssistantEngineContext = (\n requestContext: RequestContext<SurveyAssistantRequestContext> | undefined,\n): SurveyAssistantEngineContext | undefined =>\n requestContext?.get(\"turnDraft\")?.context ?? requestContext?.get(\"engineContext\");\n\nexport const requireSurveyAssistantTurnState = (\n requestContext: RequestContext<SurveyAssistantRequestContext> | undefined,\n) => {\n const turnId = requestContext?.get(\"turnId\");\n const turnDraft = requestContext?.get(\"turnDraft\");\n if (!turnId || !turnDraft || turnDraft.turnId !== turnId) {\n throw new Error(\"Survey assistant turn context is unavailable or inconsistent.\");\n }\n\n return { turnId, turnDraft };\n};\n"],"mappings":";;;;;AAsFA,MAAa,gCAAgC,UAAkD;AAC7F,KAAI,UAAU,KAAA,KAAa,UAAU,KACnC,QAAO;AAGT,QAAO,qCAAqC,MAAM,MAAM;;AAG1D,MAAa,8CACX,UACiC;AACjC,KAAI,UAAU,KAAA,KAAa,UAAU,KACnC,QAAO;AAGT,QAAO,4BAA4B,MAAM,MAAM;;AAGjD,MAAa,4CACX,UACuC;AACvC,KAAI,UAAU,KAAA,KAAa,UAAU,KACnC,QAAO;AAGT,QAAO,kCAAkC,MAAM,MAAM;;AAGvD,MAAa,2CAA2C,EACtD,SACA,eAImD;AACnD,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,KAAI,CAAC,SAAU,QAAO,EAAE,QAAQ,eAAe;CAE/C,MAAM,uBAAuB,SAAS;CACtC,MAAM,sBAAsB,QAAQ,QAAQ;AAC5C,KAAI,SAAS,cAAc,QAAQ,QAAQ,UACzC,QAAO;EACL,QAAQ;EACR;EACA;EACD;CAGH,MAAM,sBAAsB,SAAS,qBAAqB,QAAQ,QAAQ;AAC1E,KAAI,SAAS,gBAAgB,QAAQ,QAAQ,YAC3C,QAAO;EACL,QAAQ;EACR;EACA;EACA;EACD;CAGH,MAAM,mBACJ,sBAAsB,uBAClB,YACA,sBAAsB,uBACpB,aACA;AAcR,QAAO;EACL,QAAQ;EACR;EACA;EACA;EACA,iBAjBA,qBAAqB,YACjB,CACE,GAAG,IAAI,KACJ,QAAQ,GAAG,SAAS,UAAU,EAAE,EAC9B,QACE,UAAU,MAAM,QAAQ,wBAAwB,MAAM,SAAS,oBACjE,CACA,SAAS,UAAW,MAAM,SAAS,CAAC,MAAM,OAAO,GAAG,EAAE,CAAE,CAC5D,CACF,GACD,EAAE;EAQN;EACD;;AAGH,MAAa,sCAAsC,EACjD,WACA,UACA,UACA,yBAAyB,MACzB,sBAAsB,qCACtB,kBACA,mBAAmB,EAAE,EACrB,gBACA,2BAWmD;CACnD,MAAM,iBAAiB,IAAI,gBAA+C;AAE1E,gBAAe,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAClD,gBAAe,IAAI,UAAU,UAAU,OAAO;AAC9C,gBAAe,IAAI,aAAa,UAAU;AAC1C,gBAAe,IAAI,YAAY,SAAS;AACxC,KAAI,SAAS,cAAc,sBAAsB;AAC/C,iBAAe,IAAI,2BAA2B,SAAS,WAAW;AAClE,iBAAe,IAAI,sBAAsB,SAAS,SAAS;;AAE7D,gBAAe,IAAI,wBAAwB,SAAS,WAAW;AAC/D,gBAAe,IAAI,sBAAsB,SAAS,SAAS;CAC3D,MAAM,qBAAqB,wCAAwC;EACjE,SAAS;EACT,UAAU;EACX,CAAC;AACF,KAAI,mBACF,gBAAe,IAAI,sBAAsB,mBAAmB;AAE9D,KAAI,oBAAoB,iBAAiB,UAAU,SAAS,EAC1D,gBAAe,IAAI,oBAAoB,iBAAiB;AAE1D,KAAI,iBAAiB,SAAS,EAC5B,gBAAe,IAAI,oBAAoB,iBAAiB;AAE1D,KAAI,eACF,gBAAe,IAAI,kBAAkB,eAAe;AAEtD,KAAI,qBACF,gBAAe,IAAI,wBAAwB,qBAAqB;AAGlE,KAAI,CAAC,SACH,QAAO;CAGT,MAAM,gBAAgB,mCAAmC,UAAU,oBAAoB;AACvF,WAAU,WAAW,cAAc;AACnC,gBAAe,IAAI,oBAAoB,SAAS;AAChD,gBAAe,IAAI,iBAAiB,cAAc;AAElD,QAAO;;;;;;;AAQT,MAAa,mCACX,mBAEA,gBAAgB,IAAI,YAAY,EAAE,WAAW,gBAAgB,IAAI,gBAAgB;AAEnF,MAAa,mCACX,mBACG;CACH,MAAM,SAAS,gBAAgB,IAAI,SAAS;CAC5C,MAAM,YAAY,gBAAgB,IAAI,YAAY;AAClD,KAAI,CAAC,UAAU,CAAC,aAAa,UAAU,WAAW,OAChD,OAAM,IAAI,MAAM,gEAAgE;AAGlF,QAAO;EAAE;EAAQ;EAAW"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"request-guardrails-TmgQtqp4.mjs","names":[],"sources":["../src/server/request-guardrails.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\nimport { SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES } from \"../protocol/attachments\";\n\nexport const SURVEY_ASSISTANT_MAX_REQUEST_BYTES = 20 * 1024 * 1024;\nexport const SURVEY_ASSISTANT_MAX_MESSAGES = 32;\nexport const SURVEY_ASSISTANT_MAX_ATTACHMENTS = 4;\nexport const SURVEY_ASSISTANT_MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\nexport const SURVEY_ASSISTANT_MAX_TOTAL_ATTACHMENT_BYTES = 12 * 1024 * 1024;\nexport const SURVEY_ASSISTANT_MAX_TEXT_PART_CHARS = 64_000;\nexport const SURVEY_ASSISTANT_MAX_ATTACHMENT_NAME_CHARS = 255;\n\nexport const SURVEY_ASSISTANT_ACCEPTED_ATTACHMENT_TYPES = new Set<string>(\n SURVEY_ASSISTANT_ATTACHMENT_MEDIA_TYPES,\n);\n\nexport interface SurveyAssistantRequestLimits {\n acceptedAttachmentTypes: ReadonlySet<string>;\n maxAttachmentBytes: number;\n maxAttachmentNameChars: number;\n maxAttachments: number;\n maxMessages: number;\n maxRequestBytes: number;\n maxTextPartChars: number;\n maxTotalAttachmentBytes: number;\n}\n\nexport const SURVEY_ASSISTANT_DEFAULT_REQUEST_LIMITS: SurveyAssistantRequestLimits = Object.freeze({\n acceptedAttachmentTypes: SURVEY_ASSISTANT_ACCEPTED_ATTACHMENT_TYPES,\n maxAttachmentBytes: SURVEY_ASSISTANT_MAX_ATTACHMENT_BYTES,\n maxAttachmentNameChars: SURVEY_ASSISTANT_MAX_ATTACHMENT_NAME_CHARS,\n maxAttachments: SURVEY_ASSISTANT_MAX_ATTACHMENTS,\n maxMessages: SURVEY_ASSISTANT_MAX_MESSAGES,\n maxRequestBytes: SURVEY_ASSISTANT_MAX_REQUEST_BYTES,\n maxTextPartChars: SURVEY_ASSISTANT_MAX_TEXT_PART_CHARS,\n maxTotalAttachmentBytes: SURVEY_ASSISTANT_MAX_TOTAL_ATTACHMENT_BYTES,\n});\n\nexport class SurveyAssistantRequestError extends Error {\n readonly status: number;\n\n constructor(message: string, status = 400) {\n super(message);\n this.name = \"SurveyAssistantRequestError\";\n this.status = status;\n }\n}\n\nconst attachmentSignatures: Record<string, (bytes: Uint8Array) => boolean> = {\n \"application/pdf\": (bytes) =>\n bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46,\n \"image/jpeg\": (bytes) => bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff,\n \"image/png\": (bytes) =>\n [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every(\n (value, index) => bytes[index] === value,\n ),\n};\n\nconst decodeBase64Prefix = (payload: string) => {\n try {\n const binary = atob(payload.slice(0, 24));\n return Uint8Array.from(binary, (character) => character.charCodeAt(0));\n } catch {\n throw new SurveyAssistantRequestError(\"The survey assistant attachment is malformed.\");\n }\n};\n\nconst getDataUrlBytes = (url: string, expectedMediaType: string): number => {\n const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/.exec(url);\n if (!match) {\n throw new SurveyAssistantRequestError(\"Attachments must be base64 data URLs.\");\n }\n if (match[1]?.toLowerCase() !== expectedMediaType.toLowerCase()) {\n throw new SurveyAssistantRequestError(\"Attachment media type does not match its data URL.\");\n }\n\n const payload = match[2] ?? \"\";\n const signatureMatches = attachmentSignatures[expectedMediaType.toLowerCase()];\n if (signatureMatches && !signatureMatches(decodeBase64Prefix(payload))) {\n throw new SurveyAssistantRequestError(\n \"Attachment content does not match its declared media type.\",\n );\n }\n const padding = payload.endsWith(\"==\") ? 2 : payload.endsWith(\"=\") ? 1 : 0;\n return Math.max(0, Math.floor((payload.length * 3) / 4) - padding);\n};\n\nexport const validateSurveyAssistantMessages = (\n value: unknown,\n limits: SurveyAssistantRequestLimits = SURVEY_ASSISTANT_DEFAULT_REQUEST_LIMITS,\n): UIMessage[] => {\n if (!Array.isArray(value) || value.length === 0) {\n throw new SurveyAssistantRequestError(\"A survey assistant message is required.\");\n }\n if (value.length > limits.maxMessages) {\n throw new SurveyAssistantRequestError(\n \"The survey assistant request contains too many messages.\",\n );\n }\n if (value.length !== 1 || value[0]?.role !== \"user\") {\n throw new SurveyAssistantRequestError(\n \"The survey assistant request must contain only the current user message.\",\n );\n }\n\n let attachmentCount = 0;\n let totalAttachmentBytes = 0;\n for (const message of value) {\n if (!message || typeof message !== \"object\" || !Array.isArray(message.parts)) {\n throw new SurveyAssistantRequestError(\"The survey assistant messages are malformed.\");\n }\n\n for (const part of message.parts) {\n if (!part || typeof part !== \"object\" || typeof part.type !== \"string\") {\n throw new SurveyAssistantRequestError(\"A survey assistant message part is malformed.\");\n }\n if (part.type === \"text\") {\n if (typeof part.text !== \"string\") {\n throw new SurveyAssistantRequestError(\"A survey assistant text part is malformed.\");\n }\n if (part.text.length > limits.maxTextPartChars) {\n throw new SurveyAssistantRequestError(\"A survey assistant message is too long.\");\n }\n continue;\n }\n if (part.type !== \"file\") {\n throw new SurveyAssistantRequestError(\n \"The survey assistant message part is not supported.\",\n );\n }\n\n attachmentCount += 1;\n if (attachmentCount > limits.maxAttachments) {\n throw new SurveyAssistantRequestError(\n \"The survey assistant request has too many attachments.\",\n );\n }\n if (\n typeof part.mediaType !== \"string\" ||\n !limits.acceptedAttachmentTypes.has(part.mediaType.toLowerCase())\n ) {\n throw new SurveyAssistantRequestError(\n \"The survey assistant attachment type is not supported. Upload a PDF, PNG, or JPEG file.\",\n );\n }\n if (typeof part.url !== \"string\") {\n throw new SurveyAssistantRequestError(\"The survey assistant attachment is malformed.\");\n }\n if (\n part.filename !== undefined &&\n (typeof part.filename !== \"string\" ||\n part.filename.length > limits.maxAttachmentNameChars ||\n /[\\u0000-\\u001f\\u007f]/.test(part.filename))\n ) {\n throw new SurveyAssistantRequestError(\"The survey assistant attachment name is invalid.\");\n }\n\n const bytes = getDataUrlBytes(part.url, part.mediaType);\n if (bytes > limits.maxAttachmentBytes) {\n throw new SurveyAssistantRequestError(\"A survey assistant attachment is too large.\");\n }\n totalAttachmentBytes += bytes;\n if (totalAttachmentBytes > limits.maxTotalAttachmentBytes) {\n throw new SurveyAssistantRequestError(\n \"The survey assistant attachments are too large in total.\",\n );\n }\n }\n }\n\n return value as UIMessage[];\n};\n\nexport const readSurveyAssistantRequestJson = async (\n request: Request,\n maxRequestBytes = SURVEY_ASSISTANT_MAX_REQUEST_BYTES,\n): Promise<unknown> => {\n const declaredLength = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(declaredLength) && declaredLength > maxRequestBytes) {\n throw new SurveyAssistantRequestError(\"The survey assistant request is too large.\", 413);\n }\n\n const reader = request.body?.getReader();\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n if (reader) {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n totalBytes += value.byteLength;\n if (totalBytes > maxRequestBytes) {\n await reader.cancel();\n throw new SurveyAssistantRequestError(\"The survey assistant request is too large.\", 413);\n }\n chunks.push(value);\n }\n }\n const bytes = new Uint8Array(totalBytes);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n const text = new TextDecoder().decode(bytes);\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw new SurveyAssistantRequestError(\"The survey assistant request is not valid JSON.\");\n }\n};\n"],"mappings":"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"}
|