@terreno/blocks 57.10.1
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/LICENSE +21 -0
- package/README.md +62 -0
- package/biome.jsonc +18 -0
- package/bunfig.toml +6 -0
- package/dist/asks/errors.d.ts +77 -0
- package/dist/asks/errors.js +364 -0
- package/dist/asks/files.d.ts +53 -0
- package/dist/asks/files.js +171 -0
- package/dist/asks/formValues.d.ts +32 -0
- package/dist/asks/formValues.js +229 -0
- package/dist/asks/headless.d.ts +227 -0
- package/dist/asks/headless.js +94 -0
- package/dist/asks/jsonSchema.d.ts +6 -0
- package/dist/asks/jsonSchema.js +62 -0
- package/dist/asks/limits.d.ts +58 -0
- package/dist/asks/limits.js +61 -0
- package/dist/asks/prompt.d.ts +10 -0
- package/dist/asks/prompt.js +144 -0
- package/dist/asks/schema.d.ts +826 -0
- package/dist/asks/schema.js +864 -0
- package/dist/asks/simpleCard.d.ts +85 -0
- package/dist/asks/simpleCard.js +279 -0
- package/dist/asks/validateInput.d.ts +13 -0
- package/dist/asks/validateInput.js +19 -0
- package/dist/asks/validateResponse.d.ts +13 -0
- package/dist/asks/validateResponse.js +278 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +58 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.js +106 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +111 -0
- package/dist/jsonSchema.d.ts +5 -0
- package/dist/jsonSchema.js +12 -0
- package/dist/limits.d.ts +27 -0
- package/dist/limits.js +30 -0
- package/dist/lint.d.ts +21 -0
- package/dist/lint.js +388 -0
- package/dist/parse.d.ts +16 -0
- package/dist/parse.js +95 -0
- package/dist/parsePartial.d.ts +12 -0
- package/dist/parsePartial.js +105 -0
- package/dist/prompt.d.ts +9 -0
- package/dist/prompt.js +95 -0
- package/dist/schema.d.ts +199 -0
- package/dist/schema.js +277 -0
- package/dist/validate.d.ts +17 -0
- package/dist/validate.js +331 -0
- package/package.json +60 -0
- package/schemas/pendingAsksResponse.schema.json +195 -0
- package/schemas/simpleCard.schema.json +135 -0
- package/schemas/turnRequest.schema.json +108 -0
- package/schemas/turnResponse.schema.json +204 -0
- package/scripts/writeJsonSchemas.ts +11 -0
- package/src/asks/docParity.test.ts +65 -0
- package/src/asks/errors.test.ts +250 -0
- package/src/asks/errors.ts +449 -0
- package/src/asks/files.test.ts +294 -0
- package/src/asks/files.ts +216 -0
- package/src/asks/fixtures/invalid/choice-blank-text.json +20 -0
- package/src/asks/fixtures/invalid/choice-default-not-in-options.json +13 -0
- package/src/asks/fixtures/invalid/choice-default-selects-two.json +16 -0
- package/src/asks/fixtures/invalid/choice-description-too-long.json +24 -0
- package/src/asks/fixtures/invalid/choice-duplicate-ids.json +17 -0
- package/src/asks/fixtures/invalid/choice-label-too-long.json +23 -0
- package/src/asks/fixtures/invalid/choice-many-bounds-not-whole.json +17 -0
- package/src/asks/fixtures/invalid/choice-many-bounds-out-of-range.json +17 -0
- package/src/asks/fixtures/invalid/choice-many-default-duplicate.json +17 -0
- package/src/asks/fixtures/invalid/choice-many-default-too-many.json +15 -0
- package/src/asks/fixtures/invalid/choice-many-max-above-options.json +14 -0
- package/src/asks/fixtures/invalid/choice-many-min-above-choices.json +13 -0
- package/src/asks/fixtures/invalid/choice-many-min-above-max.json +15 -0
- package/src/asks/fixtures/invalid/choice-missing-required.json +14 -0
- package/src/asks/fixtures/invalid/choice-mixed-errors.json +19 -0
- package/src/asks/fixtures/invalid/choice-not-an-object.json +5 -0
- package/src/asks/fixtures/invalid/choice-one-with-many-fields.json +19 -0
- package/src/asks/fixtures/invalid/choice-option-id-format.json +35 -0
- package/src/asks/fixtures/invalid/choice-options-not-objects.json +12 -0
- package/src/asks/fixtures/invalid/choice-other-label-too-long.json +25 -0
- package/src/asks/fixtures/invalid/choice-other-label-without-allow-other.json +14 -0
- package/src/asks/fixtures/invalid/choice-prompt-empty.json +12 -0
- package/src/asks/fixtures/invalid/choice-prompt-too-long.json +23 -0
- package/src/asks/fixtures/invalid/choice-select-unknown.json +12 -0
- package/src/asks/fixtures/invalid/choice-submit-label-too-long.json +13 -0
- package/src/asks/fixtures/invalid/choice-title-too-long.json +24 -0
- package/src/asks/fixtures/invalid/choice-too-few-options.json +9 -0
- package/src/asks/fixtures/invalid/choice-too-many-options.json +219 -0
- package/src/asks/fixtures/invalid/choice-unknown-key-option.json +15 -0
- package/src/asks/fixtures/invalid/choice-unknown-key-root.json +13 -0
- package/src/asks/fixtures/invalid/choice-wrong-types.json +16 -0
- package/src/asks/fixtures/invalid/confirm-emoji-label-too-long.json +13 -0
- package/src/asks/fixtures/invalid/confirm-label-too-long.json +14 -0
- package/src/asks/fixtures/invalid/confirm-labels-alike-after-trim.json +14 -0
- package/src/asks/fixtures/invalid/confirm-labels-alike.json +13 -0
- package/src/asks/fixtures/invalid/confirm-missing-prompt-blank-label.json +16 -0
- package/src/asks/fixtures/invalid/confirm-unknown-keys.json +23 -0
- package/src/asks/fixtures/invalid/confirm-wrong-types.json +18 -0
- package/src/asks/fixtures/invalid/files-accept-duplicate.json +13 -0
- package/src/asks/fixtures/invalid/files-accept-empty.json +13 -0
- package/src/asks/fixtures/invalid/files-accept-unknown.json +13 -0
- package/src/asks/fixtures/invalid/files-counts-out-of-range.json +19 -0
- package/src/asks/fixtures/invalid/files-min-above-max.json +15 -0
- package/src/asks/fixtures/invalid/files-missing-accept-unknown-key.json +17 -0
- package/src/asks/fixtures/invalid/form-default-bad-format.json +40 -0
- package/src/asks/fixtures/invalid/form-default-invalid-date.json +40 -0
- package/src/asks/fixtures/invalid/form-default-not-in-options.json +58 -0
- package/src/asks/fixtures/invalid/form-default-out-of-range.json +33 -0
- package/src/asks/fixtures/invalid/form-default-text-bounds.json +42 -0
- package/src/asks/fixtures/invalid/form-default-wrong-type.json +30 -0
- package/src/asks/fixtures/invalid/form-duplicate-field-ids.json +24 -0
- package/src/asks/fixtures/invalid/form-field-id-format.json +19 -0
- package/src/asks/fixtures/invalid/form-field-text-limits.json +33 -0
- package/src/asks/fixtures/invalid/form-keys-of-another-type.json +39 -0
- package/src/asks/fixtures/invalid/form-length-above-cap.json +30 -0
- package/src/asks/fixtures/invalid/form-min-above-max.json +32 -0
- package/src/asks/fixtures/invalid/form-no-fields.json +13 -0
- package/src/asks/fixtures/invalid/form-select-options.json +44 -0
- package/src/asks/fixtures/invalid/form-too-many-fields.json +59 -0
- package/src/asks/fixtures/invalid/form-unknown-field-type.json +27 -0
- package/src/asks/fixtures/invalid/markdown-bounds-out-of-range.json +18 -0
- package/src/asks/fixtures/invalid/markdown-initial-above-cap.json +13 -0
- package/src/asks/fixtures/invalid/markdown-max-above-cap.json +13 -0
- package/src/asks/fixtures/invalid/markdown-min-above-cap.json +13 -0
- package/src/asks/fixtures/invalid/markdown-min-above-max.json +14 -0
- package/src/asks/fixtures/invalid/markdown-placeholder-too-long.json +13 -0
- package/src/asks/fixtures/invalid/markdown-wrong-types-unknown-key.json +27 -0
- package/src/asks/fixtures/valid/choice-emoji-label-cut.json +32 -0
- package/src/asks/fixtures/valid/choice-labels-collide-after-cut.json +21 -0
- package/src/asks/fixtures/valid/choice-long-text-cut.json +41 -0
- package/src/asks/fixtures/valid/choice-many-default-below-min.json +24 -0
- package/src/asks/fixtures/valid/choice-many-every-choice-with-other.json +24 -0
- package/src/asks/fixtures/valid/choice-many-no-default.json +21 -0
- package/src/asks/fixtures/valid/choice-many-optional-no-buttons.json +20 -0
- package/src/asks/fixtures/valid/choice-many-options-default-label-collides.json +23 -0
- package/src/asks/fixtures/valid/choice-many-options-no-buttons.json +21 -0
- package/src/asks/fixtures/valid/choice-many-options-no-default.json +22 -0
- package/src/asks/fixtures/valid/choice-many-options-with-default.json +30 -0
- package/src/asks/fixtures/valid/choice-many-required-with-default.json +29 -0
- package/src/asks/fixtures/valid/choice-many-single-pick-or-other.json +29 -0
- package/src/asks/fixtures/valid/choice-many-toppings-with-default.json +36 -0
- package/src/asks/fixtures/valid/choice-max-limits.json +245 -0
- package/src/asks/fixtures/valid/choice-plan-with-default.json +42 -0
- package/src/asks/fixtures/valid/choice-two-options-no-decline.json +32 -0
- package/src/asks/fixtures/valid/choice-two-options.json +32 -0
- package/src/asks/fixtures/valid/confirm-allow-decline.json +40 -0
- package/src/asks/fixtures/valid/confirm-default-labels.json +36 -0
- package/src/asks/fixtures/valid/confirm-destructive-delete.json +41 -0
- package/src/asks/fixtures/valid/confirm-emoji-labels.json +38 -0
- package/src/asks/fixtures/valid/confirm-long-text-cut.json +40 -0
- package/src/asks/fixtures/valid/files-every-type.json +28 -0
- package/src/asks/fixtures/valid/files-one-csv-no-decline.json +16 -0
- package/src/asks/fixtures/valid/files-receipt-photos.json +24 -0
- package/src/asks/fixtures/valid/form-boolean-newsletter.json +30 -0
- package/src/asks/fixtures/valid/form-date-due.json +44 -0
- package/src/asks/fixtures/valid/form-datetime-meeting.json +44 -0
- package/src/asks/fixtures/valid/form-datetime-minutes-offset.json +44 -0
- package/src/asks/fixtures/valid/form-eight-fields.json +71 -0
- package/src/asks/fixtures/valid/form-email-contact.json +44 -0
- package/src/asks/fixtures/valid/form-invoice-details.json +68 -0
- package/src/asks/fixtures/valid/form-multiselect-channels.json +64 -0
- package/src/asks/fixtures/valid/form-number-budget.json +45 -0
- package/src/asks/fixtures/valid/form-phone-callback.json +44 -0
- package/src/asks/fixtures/valid/form-select-shipping.json +58 -0
- package/src/asks/fixtures/valid/form-text-required-default.json +47 -0
- package/src/asks/fixtures/valid/form-textarea-notes.json +23 -0
- package/src/asks/fixtures/valid/form-time-reminder.json +44 -0
- package/src/asks/fixtures/valid/form-url-website.json +30 -0
- package/src/asks/fixtures/valid/markdown-announcement-draft.json +39 -0
- package/src/asks/fixtures/valid/markdown-empty-draft.json +34 -0
- package/src/asks/fixtures/valid/markdown-initial-blank-below-min.json +24 -0
- package/src/asks/fixtures/valid/markdown-initial-over-max.json +24 -0
- package/src/asks/fixtures/valid/markdown-long-text-cut.json +37 -0
- package/src/asks/fixtures/valid/markdown-no-decline.json +28 -0
- package/src/asks/fixtures/valid/markdown-no-initial-with-min.json +25 -0
- package/src/asks/formValues.ts +320 -0
- package/src/asks/headless.test.ts +146 -0
- package/src/asks/headless.ts +130 -0
- package/src/asks/jsonSchema.test.ts +171 -0
- package/src/asks/jsonSchema.ts +89 -0
- package/src/asks/limits.ts +58 -0
- package/src/asks/prompt.test.ts +200 -0
- package/src/asks/prompt.ts +173 -0
- package/src/asks/schema.test.ts +198 -0
- package/src/asks/schema.ts +1203 -0
- package/src/asks/simpleCard.test.ts +998 -0
- package/src/asks/simpleCard.ts +341 -0
- package/src/asks/validateInput.test.ts +875 -0
- package/src/asks/validateInput.ts +24 -0
- package/src/asks/validateResponse.test.ts +740 -0
- package/src/asks/validateResponse.ts +341 -0
- package/src/blocksDocParity.test.ts +21 -0
- package/src/cli.test.ts +134 -0
- package/src/cli.ts +65 -0
- package/src/display.test.ts +68 -0
- package/src/errors.test.ts +29 -0
- package/src/errors.ts +125 -0
- package/src/fixtures/invalid/column-not-found.yaml +14 -0
- package/src/fixtures/invalid/column-type.yaml +14 -0
- package/src/fixtures/invalid/dataset-not-found.yaml +7 -0
- package/src/fixtures/invalid/dataset-too-large.yaml +20 -0
- package/src/fixtures/invalid/depth-exceeded.yaml +10 -0
- package/src/fixtures/invalid/duplicate-id.yaml +6 -0
- package/src/fixtures/invalid/invalid-enum.yaml +5 -0
- package/src/fixtures/invalid/key-order.yaml +4 -0
- package/src/fixtures/invalid/missing-required.yaml +3 -0
- package/src/fixtures/invalid/not-a-document.txt +1 -0
- package/src/fixtures/invalid/row-arity.yaml +11 -0
- package/src/fixtures/invalid/select-target.yaml +12 -0
- package/src/fixtures/invalid/table-too-wide.yaml +9 -0
- package/src/fixtures/invalid/too-many-points.yaml +8 -0
- package/src/fixtures/invalid/unknown-key.yaml +5 -0
- package/src/fixtures/invalid/unsupported-version.yaml +4 -0
- package/src/fixtures/invalid/yaml-anchor.yaml +6 -0
- package/src/fixtures/partial/cut-after-divider.yaml +5 -0
- package/src/fixtures/valid/actions.yaml +32 -0
- package/src/fixtures/valid/chart.yaml +20 -0
- package/src/fixtures/valid/layout.yaml +26 -0
- package/src/fixtures/valid/prose.yaml +4 -0
- package/src/html.test.ts +52 -0
- package/src/index.test.ts +114 -0
- package/src/index.ts +175 -0
- package/src/jsonSchema.test.ts +33 -0
- package/src/jsonSchema.ts +11 -0
- package/src/limits.ts +27 -0
- package/src/lint.test.ts +289 -0
- package/src/lint.ts +484 -0
- package/src/parse.test.ts +281 -0
- package/src/parse.ts +104 -0
- package/src/parsePartial.test.ts +53 -0
- package/src/parsePartial.ts +116 -0
- package/src/prompt.test.ts +24 -0
- package/src/prompt.ts +104 -0
- package/src/schema.ts +509 -0
- package/src/tests/askFixtures.ts +40 -0
- package/src/validate.perf.test.ts +43 -0
- package/src/validate.ts +369 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.askAllowsDecline = exports.askOutputSchemas = exports.askInputSchemaFor = exports.askKindsForSurface = exports.isCompactAskKind = exports.compactAskInputSchemas = exports.askInputSchemas = exports.filesAskResponseSchema = exports.formAskResponseSchema = exports.markdownAskResponseSchema = exports.markdownAnswerSchema = exports.confirmAskResponseSchema = exports.confirmAnswerSchema = exports.choiceAskResponseSchema = exports.askResponseSchema = exports.askCancelResponseSchema = exports.askDeclineResponseSchema = exports.askAcceptResponseSchema = exports.choiceAnswerSchema = exports.filesAnswerSchema = exports.askFileRefSchema = exports.filesAskInputSchema = exports.filesCountBounds = exports.formAnswerSchema = exports.formAnswerEnvelopeSchema = exports.formAskInputSchema = exports.formFieldSchema = exports.FORM_FIELD_TYPES = exports.markdownAskInputSchema = exports.markdownLengthBounds = exports.confirmAskInputSchema = exports.confirmButtonLabels = exports.compactChoiceAskInputSchema = exports.choiceAskInputSchema = exports.visibleLabel = exports.choiceSelectionBounds = exports.CHOICE_SELECT_MODES = exports.choiceOptionSchema = exports.ASK_CANCEL_REASONS = exports.COMPACT_ASK_KINDS = exports.ASK_SURFACES = exports.ASK_KINDS = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const files_1 = require("./files");
|
|
7
|
+
const formValues_1 = require("./formValues");
|
|
8
|
+
const limits_1 = require("./limits");
|
|
9
|
+
/** Ask kinds in the catalog. Each kind is offered to the model as the tool `ask_<kind>`. */
|
|
10
|
+
exports.ASK_KINDS = ["choice", "confirm", "markdown", "form", "files"];
|
|
11
|
+
/**
|
|
12
|
+
* Where the user answers. `compact` is a small screen, such as a watch: the model is offered only
|
|
13
|
+
* asks whose simple card shows every option, so the user never has to continue on a phone.
|
|
14
|
+
*/
|
|
15
|
+
exports.ASK_SURFACES = ["full", "compact"];
|
|
16
|
+
/** Ask kinds the compact surface offers, each with a narrowed input schema. */
|
|
17
|
+
exports.COMPACT_ASK_KINDS = ["choice", "confirm"];
|
|
18
|
+
/** Reasons the server records when it answers an ask with `cancel` on the user's behalf. */
|
|
19
|
+
exports.ASK_CANCEL_REASONS = {
|
|
20
|
+
oneAskAtATime: "one_ask_at_a_time",
|
|
21
|
+
userSentMessage: "user_sent_message",
|
|
22
|
+
};
|
|
23
|
+
const visibleText = (maxLength) => zod_1.z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(maxLength)
|
|
27
|
+
.refine((value) => value.length === 0 || value.trim().length > 0, {
|
|
28
|
+
message: "Must contain visible text, not only whitespace.",
|
|
29
|
+
params: { askCode: "TOO_SHORT" },
|
|
30
|
+
});
|
|
31
|
+
const sharedAskFields = {
|
|
32
|
+
allowDecline: zod_1.z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Show a Skip button so the user can decline to answer. Defaults to true."),
|
|
36
|
+
prompt: visibleText(limits_1.ASK_LIMITS.promptMaxLength).describe(`The question for the user, in plain text: no markdown and no links. 1-${limits_1.ASK_LIMITS.promptMaxLength} characters.`),
|
|
37
|
+
submitLabel: visibleText(limits_1.ASK_LIMITS.submitLabelMaxLength)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe(`Label for the submit button, at most ${limits_1.ASK_LIMITS.submitLabelMaxLength} characters. Defaults to "Submit".`),
|
|
40
|
+
title: visibleText(limits_1.ASK_LIMITS.titleMaxLength)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe(`A short heading above the question, at most ${limits_1.ASK_LIMITS.titleMaxLength} characters.`),
|
|
43
|
+
};
|
|
44
|
+
const choiceOption = (labelMaxLength) => zod_1.z
|
|
45
|
+
.object({
|
|
46
|
+
description: visibleText(limits_1.ASK_LIMITS.choice.optionDescriptionMaxLength)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe(`One line under the label, at most ${limits_1.ASK_LIMITS.choice.optionDescriptionMaxLength} characters.`),
|
|
49
|
+
id: zod_1.z
|
|
50
|
+
.string()
|
|
51
|
+
.regex(limits_1.ASK_LIMITS.choice.optionIdPattern)
|
|
52
|
+
.describe(`Stable id returned in the answer: 1-${limits_1.ASK_LIMITS.choice.optionIdMaxLength} lowercase letters, digits, "_", or "-", starting with a letter or digit. Unique within the ask.`),
|
|
53
|
+
label: visibleText(labelMaxLength).describe(`What the user sees, at most ${labelMaxLength} characters.`),
|
|
54
|
+
})
|
|
55
|
+
.strict();
|
|
56
|
+
exports.choiceOptionSchema = choiceOption(limits_1.ASK_LIMITS.choice.optionLabelMaxLength);
|
|
57
|
+
/** How many options a `choice` lets the user pick. */
|
|
58
|
+
exports.CHOICE_SELECT_MODES = ["one", "many"];
|
|
59
|
+
/**
|
|
60
|
+
* How many choices an answer to a `choice` must make, counting a typed Other answer as one.
|
|
61
|
+
* `select: "one"` is always exactly one. `select: "many"` defaults to at least 1 and at most every
|
|
62
|
+
* choice the ask offers: each option, plus Other when `allowOther` is true.
|
|
63
|
+
*/
|
|
64
|
+
const choiceSelectionBounds = (input) => {
|
|
65
|
+
if (input.select === "one") {
|
|
66
|
+
return { max: 1, min: 1 };
|
|
67
|
+
}
|
|
68
|
+
const choiceCount = input.options.length + (input.allowOther === true ? 1 : 0);
|
|
69
|
+
return { max: input.maxSelected ?? choiceCount, min: input.minSelected ?? 1 };
|
|
70
|
+
};
|
|
71
|
+
exports.choiceSelectionBounds = choiceSelectionBounds;
|
|
72
|
+
const pluralOptions = (count) => (count === 1 ? "1 choice" : `${count} choices`);
|
|
73
|
+
const checkDuplicateIds = ({ ctx, fix, ids, message, segments, }) => {
|
|
74
|
+
const firstIndexById = new Map();
|
|
75
|
+
ids.forEach((id, index) => {
|
|
76
|
+
const firstIndex = firstIndexById.get(id);
|
|
77
|
+
if (firstIndex === undefined) {
|
|
78
|
+
firstIndexById.set(id, index);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
82
|
+
code: "DUPLICATE_ID",
|
|
83
|
+
fix,
|
|
84
|
+
message: message(id, firstIndex),
|
|
85
|
+
segments: segments(index),
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
return firstIndexById;
|
|
89
|
+
};
|
|
90
|
+
/** `select: "one"` picks exactly one option; count bounds and Other belong to `select: "many"`. */
|
|
91
|
+
const checkSelectOne = (input, ctx) => {
|
|
92
|
+
for (const field of ["minSelected", "maxSelected"]) {
|
|
93
|
+
const value = input[field];
|
|
94
|
+
if (value === undefined || value === 1) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
98
|
+
code: "RANGE_INVALID",
|
|
99
|
+
fix: `Remove ${field}, or use select "many".`,
|
|
100
|
+
message: `select "one" picks exactly one option, so ${field} must be 1, not ${value}.`,
|
|
101
|
+
segments: [field],
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
if (input.allowOther === true) {
|
|
105
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
106
|
+
code: "OTHER_NOT_ALLOWED",
|
|
107
|
+
fix: 'Use select "many" (with maxSelected 1 for a single pick), or remove allowOther.',
|
|
108
|
+
message: 'allowOther needs select "many".',
|
|
109
|
+
segments: ["allowOther"],
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
const defaults = input.default ?? [];
|
|
113
|
+
if (defaults.length > 1) {
|
|
114
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
115
|
+
code: "SELECTION_COUNT",
|
|
116
|
+
fix: "Keep one option id in default, or remove default.",
|
|
117
|
+
message: `default lists ${defaults.length} option ids, but select "one" allows one.`,
|
|
118
|
+
segments: ["default"],
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const checkSelectMany = (input, ctx) => {
|
|
123
|
+
const choiceCount = input.options.length + (input.allowOther === true ? 1 : 0);
|
|
124
|
+
const { max, min } = (0, exports.choiceSelectionBounds)(input);
|
|
125
|
+
if (input.maxSelected !== undefined && input.maxSelected > choiceCount) {
|
|
126
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
127
|
+
code: "RANGE_INVALID",
|
|
128
|
+
fix: `Set maxSelected to ${choiceCount} or fewer, or add options.`,
|
|
129
|
+
message: `maxSelected is ${input.maxSelected}, but the ask offers ${pluralOptions(choiceCount)}.`,
|
|
130
|
+
segments: ["maxSelected"],
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
if (min > Math.min(max, choiceCount)) {
|
|
134
|
+
const isMaxSelectedBinding = input.maxSelected !== undefined && input.maxSelected <= choiceCount;
|
|
135
|
+
const limit = isMaxSelectedBinding
|
|
136
|
+
? `maxSelected (${max})`
|
|
137
|
+
: `the ${pluralOptions(choiceCount)} the ask offers`;
|
|
138
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
139
|
+
code: "RANGE_INVALID",
|
|
140
|
+
fix: "Lower minSelected, or raise maxSelected and add options.",
|
|
141
|
+
message: `minSelected (${min}) is more than ${limit}.`,
|
|
142
|
+
segments: ["minSelected"],
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
const defaults = input.default ?? [];
|
|
146
|
+
if (defaults.length > max) {
|
|
147
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
148
|
+
code: "SELECTION_COUNT",
|
|
149
|
+
fix: `Keep at most ${max} option ids in default.`,
|
|
150
|
+
message: `default lists ${defaults.length} option ids, but the ask allows at most ${max}.`,
|
|
151
|
+
segments: ["default"],
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
checkDuplicateIds({
|
|
155
|
+
ctx,
|
|
156
|
+
fix: "List each option id in default once.",
|
|
157
|
+
ids: defaults,
|
|
158
|
+
message: (id, firstIndex) => `Default ${(0, errors_1.quoteValue)(id)} is already listed at ${(0, errors_1.formatAskPath)(["default", firstIndex])}.`,
|
|
159
|
+
segments: (index) => ["default", index],
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
const checkChoiceInput = (input, ctx) => {
|
|
163
|
+
const firstIndexById = checkDuplicateIds({
|
|
164
|
+
ctx,
|
|
165
|
+
fix: "Give every option a unique id.",
|
|
166
|
+
ids: input.options.map((option) => option.id),
|
|
167
|
+
message: (id, firstIndex) => `Option id ${(0, errors_1.quoteValue)(id)} is already used by ${(0, errors_1.formatAskPath)(["options", firstIndex])}.`,
|
|
168
|
+
segments: (index) => ["options", index, "id"],
|
|
169
|
+
});
|
|
170
|
+
if (input.select === "many") {
|
|
171
|
+
checkSelectMany(input, ctx);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
checkSelectOne(input, ctx);
|
|
175
|
+
}
|
|
176
|
+
if (input.otherLabel !== undefined && input.allowOther !== true) {
|
|
177
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
178
|
+
code: "OTHER_NOT_ALLOWED",
|
|
179
|
+
fix: "Set allowOther to true, or remove otherLabel.",
|
|
180
|
+
message: "otherLabel is set, but allowOther is not true.",
|
|
181
|
+
segments: ["otherLabel"],
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
const defaults = input.default ?? [];
|
|
185
|
+
defaults.forEach((id, index) => {
|
|
186
|
+
if (firstIndexById.has(id)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
190
|
+
code: "DEFAULT_NOT_IN_OPTIONS",
|
|
191
|
+
fix: "Use an id from options, or remove it from default.",
|
|
192
|
+
message: `Default ${(0, errors_1.quoteValue)(id)} is not the id of any option.`,
|
|
193
|
+
segments: ["default", index],
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* A compact ask shows each option as a button with its label uncut, so two options with the same
|
|
199
|
+
* label would be two buttons the user cannot tell apart. Zod counts a label's length in code points,
|
|
200
|
+
* but a simple card cuts labels by UTF-16 units, so a label with emoji can fit Zod's limit and still
|
|
201
|
+
* be cut on its button.
|
|
202
|
+
*/
|
|
203
|
+
const checkButtonLabelLength = ({ ctx, label, segments, }) => {
|
|
204
|
+
const { buttonLabelMaxLength } = limits_1.ASK_LIMITS.simpleCard;
|
|
205
|
+
if (label.length <= buttonLabelMaxLength) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const path = (0, errors_1.formatAskPath)(segments);
|
|
209
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
210
|
+
code: "TOO_LONG",
|
|
211
|
+
fix: `Shorten ${path} to ${buttonLabelMaxLength} characters or fewer, or use fewer emoji.`,
|
|
212
|
+
message: `${path} is longer than ${buttonLabelMaxLength} characters, counting each emoji as 2 or more.`,
|
|
213
|
+
segments,
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* A label as its button shows it. Spaces at either end don't show, so "Go " and "Go" look the same
|
|
218
|
+
* and are compared, and put on simple card buttons, without them.
|
|
219
|
+
*/
|
|
220
|
+
const visibleLabel = (label) => label.trim();
|
|
221
|
+
exports.visibleLabel = visibleLabel;
|
|
222
|
+
/** Says why two labels that differ only in spaces at either end count as the same. */
|
|
223
|
+
const spacesNote = (label, otherLabel) => label === otherLabel ? "" : ", ignoring spaces at either end";
|
|
224
|
+
const checkCompactChoiceInput = (input, ctx) => {
|
|
225
|
+
checkChoiceInput(input, ctx);
|
|
226
|
+
const firstIndexByLabel = new Map();
|
|
227
|
+
input.options.forEach((option, index) => {
|
|
228
|
+
checkButtonLabelLength({ ctx, label: option.label, segments: ["options", index, "label"] });
|
|
229
|
+
const firstIndex = firstIndexByLabel.get((0, exports.visibleLabel)(option.label));
|
|
230
|
+
if (firstIndex === undefined) {
|
|
231
|
+
firstIndexByLabel.set((0, exports.visibleLabel)(option.label), index);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const note = spacesNote(option.label, input.options[firstIndex].label);
|
|
235
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
236
|
+
code: "DUPLICATE_LABEL",
|
|
237
|
+
fix: "Give every option a different label.",
|
|
238
|
+
message: `Option label ${(0, errors_1.quoteValue)(option.label)} is already used by ${(0, errors_1.formatAskPath)(["options", firstIndex])}${note}.`,
|
|
239
|
+
segments: ["options", index, "label"],
|
|
240
|
+
}));
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
const choiceBaseFields = ({ defaultDescription, labelMaxLength, optionsMax, }) => ({
|
|
244
|
+
...sharedAskFields,
|
|
245
|
+
default: zod_1.z.array(zod_1.z.string()).max(optionsMax).optional().describe(defaultDescription),
|
|
246
|
+
options: zod_1.z
|
|
247
|
+
.array(choiceOption(labelMaxLength))
|
|
248
|
+
.min(limits_1.ASK_LIMITS.choice.optionsMin)
|
|
249
|
+
.max(optionsMax)
|
|
250
|
+
.describe(`The options, in display order: ${limits_1.ASK_LIMITS.choice.optionsMin}-${optionsMax} items.`),
|
|
251
|
+
});
|
|
252
|
+
/** Input for `ask_choice`: pick one option, or several with `select: "many"`, from a list. */
|
|
253
|
+
exports.choiceAskInputSchema = zod_1.z
|
|
254
|
+
.object({
|
|
255
|
+
...choiceBaseFields({
|
|
256
|
+
defaultDescription: 'Option ids to preselect. With select "one", at most one id; with select "many", at most maxSelected ids.',
|
|
257
|
+
labelMaxLength: limits_1.ASK_LIMITS.choice.optionLabelMaxLength,
|
|
258
|
+
optionsMax: limits_1.ASK_LIMITS.choice.optionsMax,
|
|
259
|
+
}),
|
|
260
|
+
allowOther: zod_1.z
|
|
261
|
+
.boolean()
|
|
262
|
+
.optional()
|
|
263
|
+
.describe(`With select "many": add a free-text Other entry. The answer's other holds its text, at most ${limits_1.ASK_LIMITS.choice.otherMaxLength} characters, and counts as one choice.`),
|
|
264
|
+
maxSelected: zod_1.z
|
|
265
|
+
.number()
|
|
266
|
+
.int()
|
|
267
|
+
.min(1)
|
|
268
|
+
.optional()
|
|
269
|
+
.describe('With select "many": the most choices the user may make. Defaults to every choice: the option count, plus 1 with allowOther.'),
|
|
270
|
+
minSelected: zod_1.z
|
|
271
|
+
.number()
|
|
272
|
+
.int()
|
|
273
|
+
.min(0)
|
|
274
|
+
.optional()
|
|
275
|
+
.describe('With select "many": the fewest choices the user must make. Defaults to 1. 0 lets the user submit no choice.'),
|
|
276
|
+
otherLabel: visibleText(limits_1.ASK_LIMITS.choice.optionLabelMaxLength)
|
|
277
|
+
.optional()
|
|
278
|
+
.describe(`Label for the Other entry when allowOther is true, at most ${limits_1.ASK_LIMITS.choice.optionLabelMaxLength} characters. Defaults to "Other".`),
|
|
279
|
+
select: zod_1.z
|
|
280
|
+
.enum(exports.CHOICE_SELECT_MODES)
|
|
281
|
+
.describe('How many options the user picks: "one", or "many" (checkboxes).'),
|
|
282
|
+
})
|
|
283
|
+
.strict()
|
|
284
|
+
.superRefine(checkChoiceInput);
|
|
285
|
+
/**
|
|
286
|
+
* Input for `ask_choice` on the compact surface: pick one option by tapping its button, so the
|
|
287
|
+
* card never hands off. At most 3 options, labels of at most 20 characters (each emoji counts as 2
|
|
288
|
+
* or more), and no two labels alike. `select: "many"` and Other are not offered.
|
|
289
|
+
*/
|
|
290
|
+
exports.compactChoiceAskInputSchema = zod_1.z
|
|
291
|
+
.object({
|
|
292
|
+
...choiceBaseFields({
|
|
293
|
+
defaultDescription: 'Option ids to preselect. With select "one", at most one id.',
|
|
294
|
+
labelMaxLength: limits_1.ASK_LIMITS.simpleCard.buttonLabelMaxLength,
|
|
295
|
+
optionsMax: limits_1.ASK_LIMITS.simpleCard.buttonsMax,
|
|
296
|
+
}),
|
|
297
|
+
select: zod_1.z.literal("one").describe('How many options the user picks. Only "one" is supported.'),
|
|
298
|
+
})
|
|
299
|
+
.strict()
|
|
300
|
+
.superRefine(checkCompactChoiceInput);
|
|
301
|
+
/** The button labels a `confirm` ask shows when it does not name its own. */
|
|
302
|
+
const CONFIRM_DEFAULT_LABELS = { confirm: "Confirm", deny: "Cancel" };
|
|
303
|
+
/** The labels of a `confirm` ask's approve and deny buttons, with the defaults filled in. */
|
|
304
|
+
const confirmButtonLabels = (input) => ({
|
|
305
|
+
confirm: input.confirmLabel ?? CONFIRM_DEFAULT_LABELS.confirm,
|
|
306
|
+
deny: input.denyLabel ?? CONFIRM_DEFAULT_LABELS.deny,
|
|
307
|
+
});
|
|
308
|
+
exports.confirmButtonLabels = confirmButtonLabels;
|
|
309
|
+
/**
|
|
310
|
+
* Both labels fit a simple card button uncut, so every surface shows the same words, and they
|
|
311
|
+
* differ, so the user can tell approve from deny.
|
|
312
|
+
*/
|
|
313
|
+
const checkConfirmInput = (input, ctx) => {
|
|
314
|
+
for (const field of ["confirmLabel", "denyLabel"]) {
|
|
315
|
+
const label = input[field];
|
|
316
|
+
if (label !== undefined) {
|
|
317
|
+
checkButtonLabelLength({ ctx, label, segments: [field] });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const { confirm, deny } = (0, exports.confirmButtonLabels)(input);
|
|
321
|
+
if ((0, exports.visibleLabel)(confirm) !== (0, exports.visibleLabel)(deny)) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const note = spacesNote(confirm, deny);
|
|
325
|
+
if (input.denyLabel === undefined) {
|
|
326
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
327
|
+
code: "DUPLICATE_LABEL",
|
|
328
|
+
fix: `Give confirmLabel a label other than ${(0, errors_1.quoteValue)(deny)}, the deny button's label.`,
|
|
329
|
+
message: `confirmLabel ${(0, errors_1.quoteValue)(confirm)} is the same as the deny button's label${note}.`,
|
|
330
|
+
segments: ["confirmLabel"],
|
|
331
|
+
}));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
335
|
+
code: "DUPLICATE_LABEL",
|
|
336
|
+
fix: `Give denyLabel a label other than ${(0, errors_1.quoteValue)(confirm)}, the approve button's label.`,
|
|
337
|
+
message: `denyLabel ${(0, errors_1.quoteValue)(deny)} is the same as the approve button's label${note}.`,
|
|
338
|
+
segments: ["denyLabel"],
|
|
339
|
+
}));
|
|
340
|
+
};
|
|
341
|
+
const confirmLabelField = (button, fallback) => visibleText(limits_1.ASK_LIMITS.confirm.labelMaxLength)
|
|
342
|
+
.optional()
|
|
343
|
+
.describe(`Label for the ${button} button, at most ${limits_1.ASK_LIMITS.confirm.labelMaxLength} characters. Defaults to "${fallback}".`);
|
|
344
|
+
/**
|
|
345
|
+
* Input for `ask_confirm`: approve or deny one action. It has no Submit, so no `submitLabel`, and
|
|
346
|
+
* `allowDecline` defaults to false because deny is already the negative answer.
|
|
347
|
+
*/
|
|
348
|
+
exports.confirmAskInputSchema = zod_1.z
|
|
349
|
+
.object({
|
|
350
|
+
allowDecline: zod_1.z
|
|
351
|
+
.boolean()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe("Show a Skip button as well as deny. Defaults to false, because deny is the negative answer."),
|
|
354
|
+
confirmLabel: confirmLabelField("approve", CONFIRM_DEFAULT_LABELS.confirm),
|
|
355
|
+
denyLabel: confirmLabelField("deny", CONFIRM_DEFAULT_LABELS.deny),
|
|
356
|
+
destructive: zod_1.z
|
|
357
|
+
.boolean()
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("True when the action deletes data or cannot be undone. The approve button then shows as destructive. Defaults to false."),
|
|
360
|
+
prompt: sharedAskFields.prompt,
|
|
361
|
+
title: sharedAskFields.title,
|
|
362
|
+
})
|
|
363
|
+
.strict()
|
|
364
|
+
.superRefine(checkConfirmInput);
|
|
365
|
+
/**
|
|
366
|
+
* How long an answer to a `markdown` ask may be. `max` counts UTF-16 code units (the `length` of a
|
|
367
|
+
* JavaScript string, so most emoji count as 2 or more) and defaults to the 20,000 cap. `min`
|
|
368
|
+
* counts the text without spaces at either end and defaults to 0.
|
|
369
|
+
*/
|
|
370
|
+
const markdownLengthBounds = (input) => ({
|
|
371
|
+
max: input.maxLength ?? limits_1.ASK_LIMITS.markdown.maxLength,
|
|
372
|
+
min: input.minLength ?? 0,
|
|
373
|
+
});
|
|
374
|
+
exports.markdownLengthBounds = markdownLengthBounds;
|
|
375
|
+
/**
|
|
376
|
+
* Checks a draft's length against the cap in UTF-16 code units. Zod counts code points, so a draft
|
|
377
|
+
* with emoji could otherwise pass Zod and still be longer than a text field lets the user keep.
|
|
378
|
+
*/
|
|
379
|
+
const checkMarkdownCap = ({ ctx, segments, value, }) => {
|
|
380
|
+
const cap = limits_1.ASK_LIMITS.markdown.maxLength;
|
|
381
|
+
if (value.length <= cap) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const path = (0, errors_1.formatAskPath)(segments);
|
|
385
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
386
|
+
code: "TOO_LONG",
|
|
387
|
+
fix: `Shorten ${path} to ${cap} characters or fewer.`,
|
|
388
|
+
message: `${path} is longer than ${cap} characters.`,
|
|
389
|
+
segments,
|
|
390
|
+
}));
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* `minLength` must not be above `maxLength`, or no answer could be sent. An `initial` draft outside
|
|
394
|
+
* the bounds is valid: the user edits it to fit, and the simple card offers no Approve draft.
|
|
395
|
+
*/
|
|
396
|
+
const checkMarkdownInput = (input, ctx) => {
|
|
397
|
+
if (input.initial !== undefined) {
|
|
398
|
+
checkMarkdownCap({ ctx, segments: ["initial"], value: input.initial });
|
|
399
|
+
}
|
|
400
|
+
const { max, min } = (0, exports.markdownLengthBounds)(input);
|
|
401
|
+
if (min <= max) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const limit = input.maxLength === undefined ? `the ${max}-character cap` : `maxLength (${max})`;
|
|
405
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
406
|
+
code: "RANGE_INVALID",
|
|
407
|
+
fix: "Lower minLength, or raise maxLength.",
|
|
408
|
+
message: `minLength (${min}) is more than ${limit}.`,
|
|
409
|
+
segments: ["minLength"],
|
|
410
|
+
}));
|
|
411
|
+
};
|
|
412
|
+
/** Input for `ask_markdown`: the user edits a markdown draft and sends it back. */
|
|
413
|
+
exports.markdownAskInputSchema = zod_1.z
|
|
414
|
+
.object({
|
|
415
|
+
...sharedAskFields,
|
|
416
|
+
initial: zod_1.z
|
|
417
|
+
.string()
|
|
418
|
+
.max(limits_1.ASK_LIMITS.markdown.maxLength)
|
|
419
|
+
.optional()
|
|
420
|
+
.describe(`The draft the user starts from, in markdown, at most ${limits_1.ASK_LIMITS.markdown.maxLength} characters. Defaults to empty.`),
|
|
421
|
+
maxLength: zod_1.z
|
|
422
|
+
.number()
|
|
423
|
+
.int()
|
|
424
|
+
.min(1)
|
|
425
|
+
.max(limits_1.ASK_LIMITS.markdown.maxLength)
|
|
426
|
+
.optional()
|
|
427
|
+
.describe(`The longest answer the user may send, in characters. Defaults to ${limits_1.ASK_LIMITS.markdown.maxLength}, the most allowed.`),
|
|
428
|
+
minLength: zod_1.z
|
|
429
|
+
.number()
|
|
430
|
+
.int()
|
|
431
|
+
.min(0)
|
|
432
|
+
.max(limits_1.ASK_LIMITS.markdown.maxLength)
|
|
433
|
+
.optional()
|
|
434
|
+
.describe("The shortest answer the user may send, in characters, not counting spaces at either end. Defaults to 0."),
|
|
435
|
+
placeholder: visibleText(limits_1.ASK_LIMITS.markdown.placeholderMaxLength)
|
|
436
|
+
.optional()
|
|
437
|
+
.describe(`Hint text shown while the editor is empty, at most ${limits_1.ASK_LIMITS.markdown.placeholderMaxLength} characters.`),
|
|
438
|
+
})
|
|
439
|
+
.strict()
|
|
440
|
+
.superRefine(checkMarkdownInput);
|
|
441
|
+
/** The field types a `form` offers. Every field is flat: no nesting and no conditional fields. */
|
|
442
|
+
exports.FORM_FIELD_TYPES = [
|
|
443
|
+
"text",
|
|
444
|
+
"textarea",
|
|
445
|
+
"email",
|
|
446
|
+
"url",
|
|
447
|
+
"phone",
|
|
448
|
+
"number",
|
|
449
|
+
"date",
|
|
450
|
+
"time",
|
|
451
|
+
"datetime",
|
|
452
|
+
"boolean",
|
|
453
|
+
"select",
|
|
454
|
+
"multiselect",
|
|
455
|
+
];
|
|
456
|
+
const formFieldBase = (type) => ({
|
|
457
|
+
helperText: visibleText(limits_1.ASK_LIMITS.form.helperTextMaxLength)
|
|
458
|
+
.optional()
|
|
459
|
+
.describe(`One line under the field, at most ${limits_1.ASK_LIMITS.form.helperTextMaxLength} characters.`),
|
|
460
|
+
id: zod_1.z
|
|
461
|
+
.string()
|
|
462
|
+
.regex(limits_1.ASK_LIMITS.choice.optionIdPattern)
|
|
463
|
+
.describe(`The key of this field's value in the answer: 1-${limits_1.ASK_LIMITS.choice.optionIdMaxLength} lowercase letters, digits, "_", or "-", starting with a letter or digit. Unique within the form.`),
|
|
464
|
+
label: visibleText(limits_1.ASK_LIMITS.form.labelMaxLength).describe(`What the user sees, at most ${limits_1.ASK_LIMITS.form.labelMaxLength} characters.`),
|
|
465
|
+
required: zod_1.z
|
|
466
|
+
.boolean()
|
|
467
|
+
.optional()
|
|
468
|
+
.describe("True when the answer must hold a non-blank value for this field. Defaults to false."),
|
|
469
|
+
type: zod_1.z.literal(type),
|
|
470
|
+
});
|
|
471
|
+
const formTextField = (type, cap) => zod_1.z
|
|
472
|
+
.object({
|
|
473
|
+
...formFieldBase(type),
|
|
474
|
+
default: zod_1.z.string().optional().describe("The text the field starts with."),
|
|
475
|
+
maxLength: zod_1.z
|
|
476
|
+
.number()
|
|
477
|
+
.int()
|
|
478
|
+
.min(1)
|
|
479
|
+
.max(cap)
|
|
480
|
+
.optional()
|
|
481
|
+
.describe(`The most characters the value may have. Defaults to ${cap}, the most allowed.`),
|
|
482
|
+
minLength: zod_1.z
|
|
483
|
+
.number()
|
|
484
|
+
.int()
|
|
485
|
+
.min(0)
|
|
486
|
+
.max(cap)
|
|
487
|
+
.optional()
|
|
488
|
+
.describe("The fewest characters a non-blank value may have, not counting spaces at either end. Defaults to 0."),
|
|
489
|
+
})
|
|
490
|
+
.strict();
|
|
491
|
+
const formStringField = (type, example) => zod_1.z
|
|
492
|
+
.object({
|
|
493
|
+
...formFieldBase(type),
|
|
494
|
+
default: zod_1.z
|
|
495
|
+
.string()
|
|
496
|
+
.optional()
|
|
497
|
+
.describe(`The value the field starts with, such as ${example}.`),
|
|
498
|
+
})
|
|
499
|
+
.strict();
|
|
500
|
+
const formDateField = (type, format) => zod_1.z
|
|
501
|
+
.object({
|
|
502
|
+
...formFieldBase(type),
|
|
503
|
+
default: zod_1.z.string().optional().describe(`The value the field starts with, as ${format}.`),
|
|
504
|
+
})
|
|
505
|
+
.strict();
|
|
506
|
+
const formOption = zod_1.z
|
|
507
|
+
.object({
|
|
508
|
+
id: exports.choiceOptionSchema.shape.id,
|
|
509
|
+
label: exports.choiceOptionSchema.shape.label,
|
|
510
|
+
})
|
|
511
|
+
.strict();
|
|
512
|
+
const formOptions = formOption
|
|
513
|
+
.array()
|
|
514
|
+
.min(limits_1.ASK_LIMITS.choice.optionsMin)
|
|
515
|
+
.max(limits_1.ASK_LIMITS.choice.optionsMax)
|
|
516
|
+
.describe(`The options, in display order: ${limits_1.ASK_LIMITS.choice.optionsMin}-${limits_1.ASK_LIMITS.choice.optionsMax} items, each {id, label}.`);
|
|
517
|
+
exports.formFieldSchema = zod_1.z.discriminatedUnion("type", [
|
|
518
|
+
formTextField("text", limits_1.ASK_LIMITS.form.textMaxLength),
|
|
519
|
+
formTextField("textarea", limits_1.ASK_LIMITS.form.textareaMaxLength),
|
|
520
|
+
formStringField("email", '"ada@example.com"'),
|
|
521
|
+
formStringField("url", '"https://example.com"'),
|
|
522
|
+
formStringField("phone", '"+1 415 555 2671"'),
|
|
523
|
+
zod_1.z
|
|
524
|
+
.object({
|
|
525
|
+
...formFieldBase("number"),
|
|
526
|
+
default: zod_1.z.number().optional().describe("The number the field starts with."),
|
|
527
|
+
integer: zod_1.z.boolean().optional().describe("True to accept whole numbers only."),
|
|
528
|
+
max: zod_1.z.number().optional().describe("The largest value allowed."),
|
|
529
|
+
min: zod_1.z.number().optional().describe("The smallest value allowed."),
|
|
530
|
+
})
|
|
531
|
+
.strict(),
|
|
532
|
+
formDateField("date", 'YYYY-MM-DD, such as "2026-10-01"'),
|
|
533
|
+
formDateField("time", '24-hour HH:mm, such as "09:30"'),
|
|
534
|
+
formDateField("datetime", 'an ISO 8601 date and time with Z or an offset, seconds optional, such as "2026-10-01T09:30Z"'),
|
|
535
|
+
zod_1.z
|
|
536
|
+
.object({
|
|
537
|
+
...formFieldBase("boolean"),
|
|
538
|
+
default: zod_1.z.boolean().optional().describe("Whether the checkbox starts checked."),
|
|
539
|
+
})
|
|
540
|
+
.strict(),
|
|
541
|
+
zod_1.z
|
|
542
|
+
.object({
|
|
543
|
+
...formFieldBase("select"),
|
|
544
|
+
default: zod_1.z.string().optional().describe("The option id selected at first."),
|
|
545
|
+
options: formOptions,
|
|
546
|
+
})
|
|
547
|
+
.strict(),
|
|
548
|
+
zod_1.z
|
|
549
|
+
.object({
|
|
550
|
+
...formFieldBase("multiselect"),
|
|
551
|
+
default: zod_1.z
|
|
552
|
+
.array(zod_1.z.string())
|
|
553
|
+
.optional()
|
|
554
|
+
.describe("The option ids selected at first, each listed once."),
|
|
555
|
+
options: formOptions,
|
|
556
|
+
})
|
|
557
|
+
.strict(),
|
|
558
|
+
]);
|
|
559
|
+
/**
|
|
560
|
+
* Field ids are unique, length and number bounds have their minimum at or below their maximum,
|
|
561
|
+
* option ids are unique within a field, and every default is a value its field accepts.
|
|
562
|
+
*/
|
|
563
|
+
const checkFormInput = (input, ctx) => {
|
|
564
|
+
checkDuplicateIds({
|
|
565
|
+
ctx,
|
|
566
|
+
fix: "Give every field a unique id.",
|
|
567
|
+
ids: input.fields.map((field) => field.id),
|
|
568
|
+
message: (id, firstIndex) => `Field id ${(0, errors_1.quoteValue)(id)} is already used by ${(0, errors_1.formatAskPath)(["fields", firstIndex])}.`,
|
|
569
|
+
segments: (index) => ["fields", index, "id"],
|
|
570
|
+
});
|
|
571
|
+
input.fields.forEach((field, index) => {
|
|
572
|
+
if ((field.type === "text" || field.type === "textarea") && field.minLength !== undefined) {
|
|
573
|
+
const max = (0, formValues_1.formTextMaxLength)(field);
|
|
574
|
+
if (field.minLength > max) {
|
|
575
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
576
|
+
code: "RANGE_INVALID",
|
|
577
|
+
fix: "Lower minLength, or raise maxLength.",
|
|
578
|
+
message: `minLength (${field.minLength}) is more than maxLength (${max}).`,
|
|
579
|
+
segments: ["fields", index, "minLength"],
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (field.type === "number" &&
|
|
584
|
+
field.min !== undefined &&
|
|
585
|
+
field.max !== undefined &&
|
|
586
|
+
field.min > field.max) {
|
|
587
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
588
|
+
code: "RANGE_INVALID",
|
|
589
|
+
fix: "Lower min, or raise max.",
|
|
590
|
+
message: `min (${field.min}) is more than max (${field.max}).`,
|
|
591
|
+
segments: ["fields", index, "min"],
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
if (field.type === "select" || field.type === "multiselect") {
|
|
595
|
+
checkDuplicateIds({
|
|
596
|
+
ctx,
|
|
597
|
+
fix: "Give every option of the field a unique id.",
|
|
598
|
+
ids: field.options.map((option) => option.id),
|
|
599
|
+
message: (id, firstIndex) => `Option id ${(0, errors_1.quoteValue)(id)} is already used by ${(0, errors_1.formatAskPath)(["fields", index, "options", firstIndex])}.`,
|
|
600
|
+
segments: (optionIndex) => ["fields", index, "options", optionIndex, "id"],
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
if (field.default === undefined) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
for (const draft of (0, formValues_1.checkFormValue)({
|
|
607
|
+
field,
|
|
608
|
+
isDefault: true,
|
|
609
|
+
segments: ["fields", index, "default"],
|
|
610
|
+
value: field.default,
|
|
611
|
+
})) {
|
|
612
|
+
ctx.addIssue((0, errors_1.askIssue)(draft));
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
/** Input for `ask_form`: a few flat fields the user fills in and submits at once. */
|
|
617
|
+
exports.formAskInputSchema = zod_1.z
|
|
618
|
+
.object({
|
|
619
|
+
...sharedAskFields,
|
|
620
|
+
fields: zod_1.z
|
|
621
|
+
.array(exports.formFieldSchema)
|
|
622
|
+
.min(limits_1.ASK_LIMITS.form.fieldsMin)
|
|
623
|
+
.max(limits_1.ASK_LIMITS.form.fieldsMax)
|
|
624
|
+
.describe(`The fields, in display order: ${limits_1.ASK_LIMITS.form.fieldsMin}-${limits_1.ASK_LIMITS.form.fieldsMax} items. Each has a type (${exports.FORM_FIELD_TYPES.join(", ")}).`),
|
|
625
|
+
})
|
|
626
|
+
.strict()
|
|
627
|
+
.superRefine(checkFormInput);
|
|
628
|
+
/** The `content` of an accepted answer from any client, before its values meet their fields. */
|
|
629
|
+
exports.formAnswerEnvelopeSchema = zod_1.z
|
|
630
|
+
.object({ values: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()) })
|
|
631
|
+
.strict();
|
|
632
|
+
/**
|
|
633
|
+
* The `content` of an accepted `form` answer: one value per answered field, keyed by field id.
|
|
634
|
+
* Unanswered optional fields are left out.
|
|
635
|
+
*/
|
|
636
|
+
exports.formAnswerSchema = zod_1.z
|
|
637
|
+
.object({
|
|
638
|
+
values: zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), zod_1.z.array(zod_1.z.string())])),
|
|
639
|
+
})
|
|
640
|
+
.strict();
|
|
641
|
+
/** How many files an answer to a `files` ask must carry: 1 to 10 unless the ask narrows it. */
|
|
642
|
+
const filesCountBounds = (input) => ({
|
|
643
|
+
max: input.maxFiles ?? limits_1.ASK_LIMITS.files.maxFiles,
|
|
644
|
+
min: input.minFiles ?? limits_1.ASK_LIMITS.files.minFiles,
|
|
645
|
+
});
|
|
646
|
+
exports.filesCountBounds = filesCountBounds;
|
|
647
|
+
const checkFilesInput = (input, ctx) => {
|
|
648
|
+
checkDuplicateIds({
|
|
649
|
+
ctx,
|
|
650
|
+
fix: "List each accept value once.",
|
|
651
|
+
ids: input.accept,
|
|
652
|
+
message: (value, firstIndex) => `${(0, errors_1.quoteValue)(value)} is already listed at ${(0, errors_1.formatAskPath)(["accept", firstIndex])}.`,
|
|
653
|
+
segments: (index) => ["accept", index],
|
|
654
|
+
});
|
|
655
|
+
const { max, min } = (0, exports.filesCountBounds)(input);
|
|
656
|
+
if (min <= max) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const limit = input.maxFiles === undefined ? `the ${max}-file cap` : `maxFiles (${input.maxFiles})`;
|
|
660
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
661
|
+
code: "RANGE_INVALID",
|
|
662
|
+
fix: "Lower minFiles, or raise maxFiles.",
|
|
663
|
+
message: `minFiles (${min}) is more than ${limit}.`,
|
|
664
|
+
segments: ["minFiles"],
|
|
665
|
+
}));
|
|
666
|
+
};
|
|
667
|
+
const fileCountField = (description) => zod_1.z
|
|
668
|
+
.number()
|
|
669
|
+
.int()
|
|
670
|
+
.min(limits_1.ASK_LIMITS.files.minFiles)
|
|
671
|
+
.max(limits_1.ASK_LIMITS.files.maxFiles)
|
|
672
|
+
.optional()
|
|
673
|
+
.describe(description);
|
|
674
|
+
/** Input for `ask_files`: the user uploads one or more images or documents. */
|
|
675
|
+
exports.filesAskInputSchema = zod_1.z
|
|
676
|
+
.object({
|
|
677
|
+
...sharedAskFields,
|
|
678
|
+
accept: zod_1.z
|
|
679
|
+
.array(zod_1.z.enum(files_1.ASK_FILE_ACCEPT))
|
|
680
|
+
.min(1)
|
|
681
|
+
.max(files_1.ASK_FILE_ACCEPT.length)
|
|
682
|
+
.describe('The kinds of file the user may send, each listed once: "image" (JPEG, PNG, GIF, WebP), "pdf", "text" (plain text), "csv", or "json".'),
|
|
683
|
+
maxFiles: fileCountField(`The most files the user may send: ${limits_1.ASK_LIMITS.files.minFiles}-${limits_1.ASK_LIMITS.files.maxFiles}. Defaults to ${limits_1.ASK_LIMITS.files.maxFiles}.`),
|
|
684
|
+
minFiles: fileCountField(`The fewest files the user must send: ${limits_1.ASK_LIMITS.files.minFiles}-${limits_1.ASK_LIMITS.files.maxFiles}. Defaults to ${limits_1.ASK_LIMITS.files.minFiles}.`),
|
|
685
|
+
})
|
|
686
|
+
.strict()
|
|
687
|
+
.superRefine(checkFilesInput);
|
|
688
|
+
/**
|
|
689
|
+
* Exactly one of `fileId` and `url` names the bytes. A `url` must be a base64 `data:` URL whose
|
|
690
|
+
* media type is the declared `mimeType`, so the server never fetches a remote URL.
|
|
691
|
+
*/
|
|
692
|
+
const checkFileRef = (ref, ctx) => {
|
|
693
|
+
if (ref.fileId === undefined && ref.url === undefined) {
|
|
694
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
695
|
+
code: "MISSING_REQUIRED",
|
|
696
|
+
fix: 'Add "fileId" (an upload from POST /files/upload) or "url" (a base64 data: URL).',
|
|
697
|
+
message: "A file needs a fileId or a url.",
|
|
698
|
+
segments: [],
|
|
699
|
+
}));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (ref.fileId !== undefined && ref.url !== undefined) {
|
|
703
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
704
|
+
code: "INVALID_FORMAT",
|
|
705
|
+
fix: "Send fileId or url, not both.",
|
|
706
|
+
message: "A file has both a fileId and a url.",
|
|
707
|
+
segments: ["url"],
|
|
708
|
+
}));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (ref.url !== undefined && (0, files_1.parseAskDataUrl)(ref.url) === undefined) {
|
|
712
|
+
ctx.addIssue((0, errors_1.askIssue)({
|
|
713
|
+
code: "INVALID_FORMAT",
|
|
714
|
+
fix: 'Send the bytes as a base64 data: URL, such as "data:image/png;base64,...".',
|
|
715
|
+
message: "url is not a base64 data: URL.",
|
|
716
|
+
segments: ["url"],
|
|
717
|
+
}));
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
/** One file in an accepted `files` answer: an upload (`fileId`) or inline bytes (`url`). */
|
|
721
|
+
exports.askFileRefSchema = zod_1.z
|
|
722
|
+
.object({
|
|
723
|
+
fileId: zod_1.z
|
|
724
|
+
.string()
|
|
725
|
+
.min(1)
|
|
726
|
+
.max(64)
|
|
727
|
+
.optional()
|
|
728
|
+
.describe("The id of the user's upload from POST /files/upload."),
|
|
729
|
+
filename: visibleText(limits_1.ASK_LIMITS.files.filenameMaxLength).describe("The file's name as the user picked it."),
|
|
730
|
+
mimeType: zod_1.z.string().min(1).max(127).describe('The file\'s MIME type, such as "image/png".'),
|
|
731
|
+
size: zod_1.z.number().int().min(0).describe("The file's size in bytes."),
|
|
732
|
+
url: zod_1.z.string().optional().describe("The file's bytes as a base64 data: URL."),
|
|
733
|
+
})
|
|
734
|
+
.strict()
|
|
735
|
+
.superRefine(checkFileRef);
|
|
736
|
+
/** The `content` of an accepted `files` answer: the files the user sent, in the order they chose. */
|
|
737
|
+
exports.filesAnswerSchema = zod_1.z.object({ files: zod_1.z.array(exports.askFileRefSchema) }).strict();
|
|
738
|
+
/** The `content` of an accepted `choice` answer. */
|
|
739
|
+
exports.choiceAnswerSchema = zod_1.z
|
|
740
|
+
.object({
|
|
741
|
+
other: visibleText(limits_1.ASK_LIMITS.choice.otherMaxLength).optional(),
|
|
742
|
+
selected: zod_1.z.array(zod_1.z.string()).max(limits_1.ASK_LIMITS.choice.optionsMax),
|
|
743
|
+
})
|
|
744
|
+
.strict();
|
|
745
|
+
exports.askAcceptResponseSchema = zod_1.z
|
|
746
|
+
.object({
|
|
747
|
+
action: zod_1.z.literal("accept"),
|
|
748
|
+
content: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
749
|
+
})
|
|
750
|
+
.strict();
|
|
751
|
+
exports.askDeclineResponseSchema = zod_1.z.object({ action: zod_1.z.literal("decline") }).strict();
|
|
752
|
+
exports.askCancelResponseSchema = zod_1.z
|
|
753
|
+
.object({
|
|
754
|
+
action: zod_1.z.literal("cancel"),
|
|
755
|
+
reason: visibleText(limits_1.ASK_LIMITS.cancelReasonMaxLength).optional(),
|
|
756
|
+
})
|
|
757
|
+
.strict();
|
|
758
|
+
/**
|
|
759
|
+
* The answer envelope for every ask (the MCP elicitation shape). `accept` carries the kind's
|
|
760
|
+
* answer in `content`; `decline` means the user pressed Skip; `cancel` means the ask was dropped.
|
|
761
|
+
*/
|
|
762
|
+
exports.askResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
763
|
+
exports.askAcceptResponseSchema,
|
|
764
|
+
exports.askDeclineResponseSchema,
|
|
765
|
+
exports.askCancelResponseSchema,
|
|
766
|
+
]);
|
|
767
|
+
/** The answer envelope for `ask_choice`, with `content` typed. Used as the tool's output schema. */
|
|
768
|
+
exports.choiceAskResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
769
|
+
zod_1.z.object({ action: zod_1.z.literal("accept"), content: exports.choiceAnswerSchema }).strict(),
|
|
770
|
+
exports.askDeclineResponseSchema,
|
|
771
|
+
exports.askCancelResponseSchema,
|
|
772
|
+
]);
|
|
773
|
+
/** The `content` of an accepted `confirm` answer: true approves the action, false denies it. */
|
|
774
|
+
exports.confirmAnswerSchema = zod_1.z.object({ confirmed: zod_1.z.boolean() }).strict();
|
|
775
|
+
/** The answer envelope for `ask_confirm`, with `content` typed. Used as the tool's output schema. */
|
|
776
|
+
exports.confirmAskResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
777
|
+
zod_1.z.object({ action: zod_1.z.literal("accept"), content: exports.confirmAnswerSchema }).strict(),
|
|
778
|
+
exports.askDeclineResponseSchema,
|
|
779
|
+
exports.askCancelResponseSchema,
|
|
780
|
+
]);
|
|
781
|
+
/**
|
|
782
|
+
* The `content` of an accepted `markdown` answer: the text the user sends back, and whether it
|
|
783
|
+
* differs from the ask's `initial` draft.
|
|
784
|
+
*/
|
|
785
|
+
exports.markdownAnswerSchema = zod_1.z
|
|
786
|
+
.object({
|
|
787
|
+
changed: zod_1.z.boolean(),
|
|
788
|
+
markdown: zod_1.z.string().max(limits_1.ASK_LIMITS.markdown.maxLength),
|
|
789
|
+
})
|
|
790
|
+
.strict()
|
|
791
|
+
.superRefine((answer, ctx) => {
|
|
792
|
+
checkMarkdownCap({ ctx, segments: ["markdown"], value: answer.markdown });
|
|
793
|
+
});
|
|
794
|
+
/** The answer envelope for `ask_markdown`, with `content` typed. Used as the tool's output schema. */
|
|
795
|
+
exports.markdownAskResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
796
|
+
zod_1.z.object({ action: zod_1.z.literal("accept"), content: exports.markdownAnswerSchema }).strict(),
|
|
797
|
+
exports.askDeclineResponseSchema,
|
|
798
|
+
exports.askCancelResponseSchema,
|
|
799
|
+
]);
|
|
800
|
+
/** The answer envelope for `ask_form`, with `content` typed. Used as the tool's output schema. */
|
|
801
|
+
exports.formAskResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
802
|
+
zod_1.z.object({ action: zod_1.z.literal("accept"), content: exports.formAnswerSchema }).strict(),
|
|
803
|
+
exports.askDeclineResponseSchema,
|
|
804
|
+
exports.askCancelResponseSchema,
|
|
805
|
+
]);
|
|
806
|
+
/** The answer envelope for `ask_files`, with `content` typed. Used as the tool's output schema. */
|
|
807
|
+
exports.filesAskResponseSchema = zod_1.z.discriminatedUnion("action", [
|
|
808
|
+
zod_1.z.object({ action: zod_1.z.literal("accept"), content: exports.filesAnswerSchema }).strict(),
|
|
809
|
+
exports.askDeclineResponseSchema,
|
|
810
|
+
exports.askCancelResponseSchema,
|
|
811
|
+
]);
|
|
812
|
+
/** Input schemas by kind. */
|
|
813
|
+
exports.askInputSchemas = {
|
|
814
|
+
choice: exports.choiceAskInputSchema,
|
|
815
|
+
confirm: exports.confirmAskInputSchema,
|
|
816
|
+
files: exports.filesAskInputSchema,
|
|
817
|
+
form: exports.formAskInputSchema,
|
|
818
|
+
markdown: exports.markdownAskInputSchema,
|
|
819
|
+
};
|
|
820
|
+
/** Input schemas by kind on the compact surface. Every compact input is also a valid full input. */
|
|
821
|
+
exports.compactAskInputSchemas = {
|
|
822
|
+
choice: exports.compactChoiceAskInputSchema,
|
|
823
|
+
// A confirm always fits a simple card (two buttons with labels that show uncut).
|
|
824
|
+
confirm: exports.confirmAskInputSchema,
|
|
825
|
+
};
|
|
826
|
+
const isCompactAskKind = (kind) => exports.COMPACT_ASK_KINDS.includes(kind);
|
|
827
|
+
exports.isCompactAskKind = isCompactAskKind;
|
|
828
|
+
/** The kinds a surface offers, in the given order. The compact surface drops kinds it cannot show. */
|
|
829
|
+
const askKindsForSurface = ({ kinds, surface, }) => (surface === "compact" ? kinds.filter(exports.isCompactAskKind) : [...kinds]);
|
|
830
|
+
exports.askKindsForSurface = askKindsForSurface;
|
|
831
|
+
/**
|
|
832
|
+
* The input schema for a kind on a surface. Throws for an unknown kind or surface, and when the
|
|
833
|
+
* compact surface does not offer the kind.
|
|
834
|
+
*/
|
|
835
|
+
const askInputSchemaFor = ({ kind, surface = "full", }) => {
|
|
836
|
+
if (!Object.hasOwn(exports.askInputSchemas, kind)) {
|
|
837
|
+
throw new Error(`Unknown ask kind "${String(kind)}".`);
|
|
838
|
+
}
|
|
839
|
+
if (!exports.ASK_SURFACES.includes(surface)) {
|
|
840
|
+
throw new Error(`Unknown ask surface "${String(surface)}".`);
|
|
841
|
+
}
|
|
842
|
+
if (surface === "full") {
|
|
843
|
+
return exports.askInputSchemas[kind];
|
|
844
|
+
}
|
|
845
|
+
if (!(0, exports.isCompactAskKind)(kind)) {
|
|
846
|
+
throw new Error(`The compact surface does not offer ask kind "${String(kind)}".`);
|
|
847
|
+
}
|
|
848
|
+
return exports.compactAskInputSchemas[kind];
|
|
849
|
+
};
|
|
850
|
+
exports.askInputSchemaFor = askInputSchemaFor;
|
|
851
|
+
/** Output (answer envelope) schemas by kind. */
|
|
852
|
+
exports.askOutputSchemas = {
|
|
853
|
+
choice: exports.choiceAskResponseSchema,
|
|
854
|
+
confirm: exports.confirmAskResponseSchema,
|
|
855
|
+
files: exports.filesAskResponseSchema,
|
|
856
|
+
form: exports.formAskResponseSchema,
|
|
857
|
+
markdown: exports.markdownAskResponseSchema,
|
|
858
|
+
};
|
|
859
|
+
/**
|
|
860
|
+
* Whether the user may skip the ask. `allowDecline` defaults to true, except on `confirm`, where
|
|
861
|
+
* deny is already the negative answer.
|
|
862
|
+
*/
|
|
863
|
+
const askAllowsDecline = ({ input, kind }) => kind === "confirm" ? input.allowDecline === true : input.allowDecline !== false;
|
|
864
|
+
exports.askAllowsDecline = askAllowsDecline;
|