@jxsuite/studio 3.0.0 → 4.0.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 +5 -5
- package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
- package/dist/chunks/{cf-settings-zj2cj1ar.js → cf-settings-45fgyx8y.js} +6 -5
- package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
- package/dist/chunks/content-source-t88vb2dr.js +49 -0
- package/dist/chunks/{convert-to-repeater-6vvx96n2.js → convert-to-repeater-dhw1nrz9.js} +27 -19
- package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
- package/dist/chunks/{extension-sections-kwzvpemr.js → extension-sections-3ydgjbdy.js} +32 -24
- package/dist/chunks/file-ops-rskmhxyq.js +48 -0
- package/dist/chunks/files-0cratsr0.js +92 -0
- package/dist/chunks/{format-host-8fr3545e.js → format-host-tgcmfs4p.js} +3 -3
- package/dist/chunks/fs-events-hrrg328g.js +21 -0
- package/dist/chunks/{git-panel-g01d93vc.js → git-panel-0yywyk76.js} +21 -16
- package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
- package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
- package/dist/chunks/layers-q75tj2mf.js +45 -0
- package/dist/chunks/lit-html-j055mkh8.js +22 -0
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
- package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
- package/dist/chunks/notify-yjm79n4s.js +30 -0
- package/dist/chunks/parse-24d0gay3.js +19 -0
- package/dist/chunks/platform-ba1j1gbh.js +16 -0
- package/dist/chunks/publish-panel-54jvhskq.js +28 -0
- package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
- package/dist/chunks/references-3qx8fkyp.js +29 -0
- package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
- package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
- package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
- package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
- package/dist/chunks/{studio-8ezdegcx.js → studio-1hbqnt19.js} +36 -32
- package/dist/chunks/{studio-r5xbtctf.js → studio-2zkz8evn.js} +446 -2451
- package/dist/chunks/{studio-pkp61mh8.js → studio-4pqqxbrt.js} +23 -15
- package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
- package/dist/chunks/{studio-3dkcd4c5.js → studio-5q69gfag.js} +60 -56
- package/dist/chunks/{studio-rh1cnamv.js → studio-5rywrkxz.js} +563 -133
- package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
- package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
- package/dist/chunks/{studio-xntfdy1x.js → studio-8swrjbny.js} +31 -18
- package/dist/chunks/studio-92ab2jqe.js +282 -0
- package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
- package/dist/chunks/{studio-ry1vz5sv.js → studio-9c4k4vhv.js} +15 -13
- package/dist/chunks/studio-9n4mrmnb.js +184 -0
- package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
- package/dist/chunks/{studio-2x9zbxmx.js → studio-ahjfdy6e.js} +9 -7
- package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
- package/dist/chunks/{studio-dc8s8heb.js → studio-cb12yz9w.js} +8528 -8780
- package/dist/chunks/studio-cft9r4r7.js +119 -0
- package/dist/chunks/{studio-hcxzyk3x.js → studio-eenfkt4e.js} +6 -6
- package/dist/chunks/studio-eesa1myv.js +498 -0
- package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
- package/dist/chunks/{studio-7p35254k.js → studio-ex8zyp98.js} +30 -26
- package/dist/chunks/studio-gv8vvshc.js +118 -0
- package/dist/chunks/studio-hbtp0eh1.js +1315 -0
- package/dist/chunks/{studio-a0yp7syn.js → studio-htsf0w6m.js} +29 -114
- package/dist/chunks/{studio-j595e748.js → studio-kjjsjak4.js} +139 -63
- package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
- package/dist/chunks/{studio-kfpek536.js → studio-kyh9avvv.js} +2 -2
- package/dist/chunks/{studio-g5gdqnyd.js → studio-kyk2v92a.js} +14 -14
- package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
- package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
- package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
- package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
- package/dist/chunks/{studio-p5dja96s.js → studio-p8j86wag.js} +2 -2
- package/dist/chunks/{studio-6z5eyjwn.js → studio-pdfjntbk.js} +66 -129
- package/dist/chunks/studio-q43a5y9h.js +394 -0
- package/dist/chunks/studio-q7v10cg2.js +106 -0
- package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
- package/dist/chunks/studio-r1r7afxx.js +1066 -0
- package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
- package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
- package/dist/chunks/studio-sf7zjgv1.js +91 -0
- package/dist/chunks/studio-t7qb9db8.js +665 -0
- package/dist/chunks/studio-tmc5emrw.js +8 -0
- package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
- package/dist/chunks/{studio-k4jvjcfx.js → studio-v38hmvx1.js} +7 -7
- package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
- package/dist/chunks/studio-wcy8trfx.js +263 -0
- package/dist/chunks/{studio-5efrtn1m.js → studio-wx5609xd.js} +21 -17
- package/dist/chunks/{studio-qpfq7xhm.js → studio-xrapxwwc.js} +22 -285
- package/dist/chunks/{studio-hj2se583.js → studio-yxrkyv8a.js} +41 -31
- package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
- package/dist/chunks/workspace-xpres40v.js +89 -0
- package/dist/iframe-entry.js +108 -24
- package/dist/studio.js +2362 -1695
- package/package.json +16 -15
- package/src/browse/library-pane.ts +57 -3
- package/src/canvas/canvas-render.ts +23 -1
- package/src/canvas/iframe-patch.ts +47 -21
- package/src/canvas/iframe-render.ts +22 -6
- package/src/commands/app-commands.ts +4 -0
- package/src/commands/command-args.ts +29 -0
- package/src/commands/context.ts +15 -1
- package/src/commands/defaults.ts +29 -1
- package/src/commands/levels.ts +16 -0
- package/src/commands/live-context.ts +3 -0
- package/src/content/collection-match.ts +267 -0
- package/src/content/entry-commands.ts +38 -29
- package/src/content/entry-fields.ts +6 -0
- package/src/editor/shortcuts.ts +5 -0
- package/src/files/file-ops.ts +10 -42
- package/src/files/files.ts +485 -59
- package/src/files/fs-events.ts +9 -0
- package/src/files/gitignore.ts +432 -0
- package/src/files/serialize-document.ts +60 -0
- package/src/format/convert-file.ts +511 -0
- package/src/format/format-choices.ts +199 -0
- package/src/grid/grid-view.ts +16 -2
- package/src/media/media-open.ts +58 -0
- package/src/media/media-pane.ts +310 -0
- package/src/new-project/import-tab.ts +332 -160
- package/src/new-project/new-project-modal.ts +27 -34
- package/src/packages/ensure-deps.ts +14 -7
- package/src/packages/jxsuite-update.ts +11 -11
- package/src/page-params.ts +10 -46
- package/src/panels/activity-bar.ts +45 -16
- package/src/panels/ai-chat/attached-context.ts +6 -1
- package/src/panels/ai-chat/chat-view.ts +255 -13
- package/src/panels/ai-chat/composer.ts +21 -97
- package/src/panels/ai-panel.ts +116 -2
- package/src/panels/navigator-panels.ts +7 -6
- package/src/panels/settings-menu.ts +799 -0
- package/src/panels/toolbar.ts +135 -15
- package/src/platforms/cloud.ts +20 -0
- package/src/platforms/devserver.ts +67 -33
- package/src/preview/preview-overlay.ts +226 -0
- package/src/services/ai-ask.ts +247 -0
- package/src/services/ai-import-tools.ts +494 -0
- package/src/services/ai-project-tools.ts +20 -26
- package/src/services/ai-system-prompt.ts +88 -2
- package/src/services/ai-turn-signal.ts +70 -0
- package/src/services/connection.ts +74 -0
- package/src/services/context-manager.ts +89 -0
- package/src/services/document-assistant.ts +45 -4
- package/src/services/import-client.ts +33 -6
- package/src/services/import-run.ts +158 -0
- package/src/services/import-seed.ts +120 -0
- package/src/services/jx-validate.ts +58 -6
- package/src/services/project-adoption.ts +71 -0
- package/src/services/references.ts +0 -0
- package/src/services/settings/definitions.ts +17 -0
- package/src/services/tool-executor.ts +34 -2
- package/src/settings/contexts-section.ts +45 -3
- package/src/settings/dependencies-editor.ts +43 -23
- package/src/settings/general-settings.ts +30 -12
- package/src/settings/preferences-dialog.ts +18 -41
- package/src/settings/preferences-sections.ts +57 -0
- package/src/settings/settings-document.ts +65 -9
- package/src/studio.ts +33 -6
- package/src/tabs/transact.ts +42 -19
- package/src/types.ts +103 -5
- package/src/ui/ai-model-picker.ts +162 -0
- package/src/ui/choice-field.ts +72 -0
- package/src/ui/layers.ts +83 -5
- package/src/utils/studio-utils.ts +22 -52
- package/styles/forced-colors.css +7 -0
- package/styles/overlays.css +256 -0
- package/styles/panels.css +13 -1
- package/styles/shell.css +111 -0
- package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
- package/dist/chunks/content-source-7fy9qrm0.js +0 -40
- package/dist/chunks/files-xcybanxe.js +0 -70
- package/dist/chunks/layers-panel-27essgkc.js +0 -60
- package/dist/chunks/publish-panel-09sy2bab.js +0 -22
- package/dist/chunks/quick-search-sefb61v8.js +0 -55
- package/dist/chunks/right-panel-9r32jd5j.js +0 -39
- package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
- package/dist/chunks/studio-c31yb4t6.js +0 -55
- package/src/canvas/site-style-css.ts +0 -117
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ai-ask.ts — the assistant's one way to stop and put a question to the author.
|
|
3
|
+
*
|
|
4
|
+
* The agent loop `await`s `toolRegistry.execute` (`services/tool-executor.ts`), so a tool that
|
|
5
|
+
* returns a pending promise suspends the turn. That is the whole mechanism: `ask_user` registers a
|
|
6
|
+
* question, hands back a promise, and the panel resolves it when the human answers. No new chat
|
|
7
|
+
* status, no pre-execution hook, and nothing in the published `@jxsuite/ai` changes —
|
|
8
|
+
* `finishStream` has already set the state to `"idle"` by the time tools run, so a suspended turn
|
|
9
|
+
* is idle by construction and this store is what "waiting" means.
|
|
10
|
+
*
|
|
11
|
+
* Two decisions are load-bearing and easy to get wrong on a rewrite:
|
|
12
|
+
*
|
|
13
|
+
* - **A skip is a SUCCESS.** "You decide" is a real answer to a question; only a stopped turn is a
|
|
14
|
+
* failure. Reporting a skip as an error would make the model apologise for having asked.
|
|
15
|
+
* - **The answer never becomes a user message.** `toMessagesArray` serialises the array verbatim
|
|
16
|
+
* and a provider requires a `tool` reply to follow its `tool_calls` request; a user turn spliced
|
|
17
|
+
* between them is a 400. The answer travels as the tool result, and the chat view renders it into
|
|
18
|
+
* the question's own chip.
|
|
19
|
+
*
|
|
20
|
+
* Exactly one question can be outstanding, because `runAgentLoop` executes a round's tool calls
|
|
21
|
+
* serially. The guard below is defensive, not load-bearing.
|
|
22
|
+
*
|
|
23
|
+
* @license MIT
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
|
|
27
|
+
import { reactive } from "../reactivity";
|
|
28
|
+
import { currentToolCallId, turnSignal } from "./ai-turn-signal";
|
|
29
|
+
|
|
30
|
+
import type { ToolRegistry } from "@jxsuite/ai/tools";
|
|
31
|
+
|
|
32
|
+
/** A question the assistant is waiting on. `id` is the tool-call id, so the chip and this agree. */
|
|
33
|
+
export interface PendingAsk {
|
|
34
|
+
id: string;
|
|
35
|
+
question: string;
|
|
36
|
+
/** Short choices rendered as buttons. Free text is always allowed alongside them. */
|
|
37
|
+
options: string[];
|
|
38
|
+
/** One line of why-I'm-asking, rendered under the question. */
|
|
39
|
+
context: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** How a question was settled. `answer: null` with `skipped` is the author saying "you decide". */
|
|
43
|
+
export interface AskAnswer {
|
|
44
|
+
answer: string | null;
|
|
45
|
+
skipped: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Max choices rendered as buttons; beyond this the question wants free text, not a menu. */
|
|
49
|
+
const MAX_OPTIONS = 6;
|
|
50
|
+
|
|
51
|
+
const state = reactive<{ pending: PendingAsk | null }>({ pending: null });
|
|
52
|
+
|
|
53
|
+
/** The resolver for {@link state.pending}, kept out of the reactive object (it is not render data). */
|
|
54
|
+
let settle: ((answer: AskAnswer) => void) | null = null;
|
|
55
|
+
|
|
56
|
+
/** Torn down with the question so a stopped turn does not leave a listener on a dead signal. */
|
|
57
|
+
let detachAbort: (() => void) | null = null;
|
|
58
|
+
|
|
59
|
+
function clear() {
|
|
60
|
+
state.pending = null;
|
|
61
|
+
settle = null;
|
|
62
|
+
detachAbort?.();
|
|
63
|
+
detachAbort = null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The outstanding question, or null. Reactive — read it inside an `effect()` to repaint on it.
|
|
68
|
+
*
|
|
69
|
+
* @returns {PendingAsk | null}
|
|
70
|
+
*/
|
|
71
|
+
export function pendingAsk(): PendingAsk | null {
|
|
72
|
+
return state.pending;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Whether a turn is suspended on the author. The composer's Send becomes Answer on this. */
|
|
76
|
+
export function isAwaitingAnswer(): boolean {
|
|
77
|
+
return state.pending !== null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Register a question and suspend until it is settled.
|
|
82
|
+
*
|
|
83
|
+
* @param {PendingAsk} ask
|
|
84
|
+
* @returns {Promise<AskAnswer>} Resolves on an answer, a skip, or the turn being stopped (`{
|
|
85
|
+
* answer: null, skipped: false }` — the shape the tool reports as a failure).
|
|
86
|
+
*/
|
|
87
|
+
export function askUser(ask: PendingAsk): Promise<AskAnswer> {
|
|
88
|
+
return new Promise<AskAnswer>((resolve) => {
|
|
89
|
+
state.pending = ask;
|
|
90
|
+
settle = resolve;
|
|
91
|
+
|
|
92
|
+
/* The turn's signal, not a parameter: `ToolRegistry.execute` has nowhere to pass one. Without
|
|
93
|
+
this, Stop would leave the loop awaiting a promise nothing could ever resolve. */
|
|
94
|
+
const signal = turnSignal();
|
|
95
|
+
if (signal?.aborted) {
|
|
96
|
+
clear();
|
|
97
|
+
resolve({ answer: null, skipped: false });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (signal) {
|
|
101
|
+
const onAbort = () => {
|
|
102
|
+
cancelAsk();
|
|
103
|
+
};
|
|
104
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
105
|
+
detachAbort = () => signal.removeEventListener("abort", onAbort);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Answer the outstanding question.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} text
|
|
114
|
+
* @returns {boolean} False when nothing was pending, so a caller can fall through to a normal send.
|
|
115
|
+
*/
|
|
116
|
+
export function answerAsk(text: string): boolean {
|
|
117
|
+
if (!settle) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const resolve = settle;
|
|
121
|
+
clear();
|
|
122
|
+
resolve({ answer: text, skipped: false });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Decline to answer — "you decide". Settles as a success, so the turn carries on.
|
|
128
|
+
*
|
|
129
|
+
* @returns {boolean} False when nothing was pending.
|
|
130
|
+
*/
|
|
131
|
+
export function skipAsk(): boolean {
|
|
132
|
+
if (!settle) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const resolve = settle;
|
|
136
|
+
clear();
|
|
137
|
+
resolve({ answer: null, skipped: true });
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Settle an outstanding question as unanswered. Called from `stop()` and from the turn's abort. */
|
|
142
|
+
export function cancelAsk(): void {
|
|
143
|
+
if (!settle) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const resolve = settle;
|
|
147
|
+
clear();
|
|
148
|
+
resolve({ answer: null, skipped: false });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Drop any outstanding question without settling a turn. For New Chat and for tests. */
|
|
152
|
+
export function resetAsk(): void {
|
|
153
|
+
cancelAsk();
|
|
154
|
+
clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register `ask_user` into a tool registry.
|
|
159
|
+
*
|
|
160
|
+
* The description carries the discipline the tool needs to be worth having: a question the model
|
|
161
|
+
* could have answered itself is noise, and a question about a setting the pipeline never receives
|
|
162
|
+
* is worse than noise, because the answer cannot be honoured.
|
|
163
|
+
*
|
|
164
|
+
* @param {Pick<ToolRegistry, "register">} registry
|
|
165
|
+
*/
|
|
166
|
+
export function registerAskTool(registry: Pick<ToolRegistry, "register">): void {
|
|
167
|
+
registry.register(
|
|
168
|
+
createToolDefinition({
|
|
169
|
+
name: "ask_user",
|
|
170
|
+
description:
|
|
171
|
+
"Pause and put a question to the user; the turn waits for their reply and resumes with " +
|
|
172
|
+
"it. Use it ONLY for a judgement that is genuinely theirs — which pages matter, whether to " +
|
|
173
|
+
"keep the original design, whether a low-fidelity page is acceptable. Never ask something " +
|
|
174
|
+
"you can decide yourself or discover with another tool, and never ask about an option you " +
|
|
175
|
+
"cannot act on. One question at a time; offer options when the sensible answers are a " +
|
|
176
|
+
"short list, and the user may always reply in their own words instead.",
|
|
177
|
+
parameters: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
question: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "The question, in one sentence, addressed to the user.",
|
|
183
|
+
},
|
|
184
|
+
options: {
|
|
185
|
+
type: "array",
|
|
186
|
+
items: { type: "string" },
|
|
187
|
+
description:
|
|
188
|
+
`Up to ${MAX_OPTIONS} short answers, rendered as buttons. Omit when the answer is ` +
|
|
189
|
+
"open-ended.",
|
|
190
|
+
},
|
|
191
|
+
context: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description:
|
|
194
|
+
"One line of why you are asking — what you found that made this a judgement call.",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
required: ["question"],
|
|
198
|
+
},
|
|
199
|
+
execute: async (args) => {
|
|
200
|
+
/* Everything `unknown`, and every field narrowed by `typeof` rather than by `?.`. The
|
|
201
|
+
registry's `validate()` is "a lightweight structural check, not a JSON Schema validator",
|
|
202
|
+
`strict: false` skips it entirely, and this tool may be registered into someone else's
|
|
203
|
+
registry — so an argument that is a number is a case, not an impossibility. Optional
|
|
204
|
+
chaining guards null, not type: `context?.trim()` threw on one. */
|
|
205
|
+
const { question, options, context } = args as {
|
|
206
|
+
question?: unknown;
|
|
207
|
+
options?: unknown;
|
|
208
|
+
context?: unknown;
|
|
209
|
+
};
|
|
210
|
+
if (typeof question !== "string" || !question.trim()) {
|
|
211
|
+
return toolError('Pass "question" — the question to put to the user.');
|
|
212
|
+
}
|
|
213
|
+
if (isAwaitingAnswer()) {
|
|
214
|
+
return toolError("A question is already waiting for the user.");
|
|
215
|
+
}
|
|
216
|
+
const choices = Array.isArray(options)
|
|
217
|
+
? options
|
|
218
|
+
.filter((o): o is string => typeof o === "string" && o.trim().length > 0)
|
|
219
|
+
.slice(0, MAX_OPTIONS)
|
|
220
|
+
: [];
|
|
221
|
+
|
|
222
|
+
const { answer, skipped } = await askUser({
|
|
223
|
+
context: typeof context === "string" ? context.trim() : "",
|
|
224
|
+
/* The tool-call id, so the chip the transcript draws and the question this registers are
|
|
225
|
+
the same thing. Without it a restored, permanently unanswered question would render
|
|
226
|
+
identically to the live one. */
|
|
227
|
+
id: currentToolCallId(),
|
|
228
|
+
options: choices,
|
|
229
|
+
question: question.trim(),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (skipped) {
|
|
233
|
+
/* A success: the model asked, and "you decide" is an answer. Rendering it as a failure
|
|
234
|
+
would have the model apologise for having asked a fair question. */
|
|
235
|
+
return toolSuccess(
|
|
236
|
+
{ answer: null, skipped: true },
|
|
237
|
+
"The user declined to answer; proceed with your best judgment.",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (answer === null) {
|
|
241
|
+
return toolError("The user did not answer — the turn was stopped.");
|
|
242
|
+
}
|
|
243
|
+
return toolSuccess({ answer, skipped: false }, `The user answered: "${answer}"`);
|
|
244
|
+
},
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
}
|