@subako-ai/assistant-ui 0.2.2 → 0.2.4

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/dist/chat.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ToolCallMessagePartProps } from "@assistant-ui/react";
2
2
  import { MessagePrimitive } from "@assistant-ui/react";
3
3
  import type { ComponentProps, ReactNode } from "react";
4
+ import { type WidgetRenderers } from "./input-form.js";
4
5
  import type { SubakoSession } from "./runtime.js";
5
6
  /** What the approval prompt reads off a tool-call part: the call's name, its approval, and the answer. */
6
7
  export type SubakoToolApprovalProps = Pick<ToolCallMessagePartProps, "toolName" | "approval" | "respondToApproval">;
@@ -33,17 +34,23 @@ export interface SubakoChatProps {
33
34
  * unstyled; a page with no such policy needs nothing here.
34
35
  */
35
36
  readonly nonce?: string;
37
+ /**
38
+ * The custom widgets this app draws in a form the agent puts through
39
+ * `builtin:ask_user`, by widget id. A widget with no renderer here lets the
40
+ * person tell the agent the app cannot show it.
41
+ */
42
+ readonly widgets?: WidgetRenderers;
36
43
  /** Rendered inside the runtime, which is where a tool UI registers itself. */
37
44
  readonly children?: ReactNode;
38
45
  }
39
46
  /**
40
47
  * One session as a working chat: the transcript, the tool calls with their
41
- * approvals, a composer, and a stop button while a run is under way. The
48
+ * approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
42
49
  * session is the app's own, so the chat opens no second connection; until it
43
50
  * has one the thread is empty and the composer is disabled. It exists for the
44
51
  * first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
45
52
  * and builds the rest from assistant-ui's primitives.
46
53
  */
47
- export declare function SubakoChat({ session, className, components, nonce, children }: SubakoChatProps): ReactNode;
54
+ export declare function SubakoChat({ session, className, components, nonce, widgets, children }: SubakoChatProps): ReactNode;
48
55
  export {};
49
56
  //# sourceMappingURL=chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EAIN,gBAAgB,EAEhB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAyKlD,0GAA0G;AAC1G,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,UAAU,GAAG,mBAAmB,CAAC,CAAC;AAEpH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,EAAE,uBAAuB,GAAG,SAAS,CAehH;AA8CD,0GAA0G;AAC1G,KAAK,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AA0C/F,MAAM,WAAW,eAAe;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,SAAS,CAqB1G"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EAIN,gBAAgB,EAEhB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAiMlD,0GAA0G;AAC1G,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,UAAU,GAAG,mBAAmB,CAAC,CAAC;AAEpH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,EAAE,uBAAuB,GAAG,SAAS,CAehH;AA8CD,0GAA0G;AAC1G,KAAK,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;AA0C/F,MAAM,WAAW,eAAe;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,GAAG,SAAS,CAiCnH"}
package/dist/chat.js CHANGED
@@ -5,6 +5,7 @@ import { MarkdownTextPrimitive } from "@assistant-ui/react-markdown";
5
5
  import { useSessionState } from "@subako-ai/react";
6
6
  import { createContext, useContext } from "react";
7
7
  import remarkGfm from "remark-gfm";
8
+ import { SubakoInputForm } from "./input-form.js";
8
9
  import { useSubakoRuntime } from "./runtime.js";
9
10
  /**
10
11
  * Enough style to read a conversation with no setup at all. Every surface names
@@ -89,6 +90,23 @@ const STYLE = `
89
90
  border-radius: 6px; background: var(--subako-surface); color: var(--subako-fg); }
90
91
  .subako-approval button { padding: 2px 10px; border: 1px solid var(--subako-border); border-radius: 6px;
91
92
  background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
93
+ .subako-inputs { display: flex; flex-direction: column; gap: 8px; padding: 0 8px; }
94
+ .subako-input-form { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 8px 10px;
95
+ border: 1px solid var(--subako-border); border-radius: 8px; background: var(--subako-surface);
96
+ color: var(--subako-fg); }
97
+ .subako-input-question { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: 0;
98
+ min-width: 0; }
99
+ .subako-input-prompt { padding: 0; font-weight: 600; }
100
+ .subako-input-options { display: flex; flex-direction: column; gap: 2px; }
101
+ .subako-input-option { display: flex; align-items: baseline; gap: 6px; }
102
+ .subako-input-hint { color: var(--subako-muted); font-size: 12px; }
103
+ .subako-input-text { padding: 4px 6px; border: 1px solid var(--subako-border); border-radius: 6px;
104
+ background: var(--subako-bg); color: var(--subako-fg); font: inherit; }
105
+ .subako-input-actions { display: flex; gap: 8px; }
106
+ .subako-input-actions button { padding: 2px 12px; border: 1px solid var(--subako-border); border-radius: 6px;
107
+ background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
108
+ .subako-input-actions button:disabled { opacity: 0.5; cursor: default; }
109
+ .subako-input-error { color: var(--subako-error); font-size: 12px; }
92
110
  .subako-composer { display: flex; gap: 8px; padding: 8px; border-top: 1px solid var(--subako-border);
93
111
  background: var(--subako-bg); color: var(--subako-fg); }
94
112
  .subako-input { flex: 1 1 auto; resize: none; padding: 6px 8px; border: 1px solid var(--subako-border);
@@ -111,7 +129,14 @@ div:where(.subako-chat) details:where(.subako-tool-fold),
111
129
  div:where(.subako-chat) summary:where(.subako-tool-summary),
112
130
  div:where(.subako-chat) span:where(.subako-tool-name, .subako-tool-status, .subako-writing),
113
131
  div:where(.subako-chat) pre:where(.subako-tool-args, .subako-tool-result),
114
- div:where(.subako-chat) :where(.subako-approval, .subako-composer) > button,
132
+ div:where(.subako-chat) :where(.subako-approval, .subako-composer, .subako-input-actions) > button,
133
+ div:where(.subako-chat) div:where(.subako-inputs, .subako-input-options, .subako-input-hint, .subako-input-actions,
134
+ .subako-input-error),
135
+ div:where(.subako-chat) :where(.subako-input-form):is(form, div),
136
+ div:where(.subako-chat) fieldset:where(.subako-input-question),
137
+ div:where(.subako-chat) legend:where(.subako-input-prompt),
138
+ div:where(.subako-chat) label:where(.subako-input-option),
139
+ div:where(.subako-chat) :where(.subako-input-text):is(input, textarea),
115
140
  div:where(.subako-chat) form:where(.subako-composer),
116
141
  div:where(.subako-chat) textarea:where(.subako-input),
117
142
  div:where(.subako-chat) textarea:where(.subako-input)::placeholder { all: revert-layer; }
@@ -213,15 +238,15 @@ function AssistantMessage() {
213
238
  }
214
239
  /**
215
240
  * One session as a working chat: the transcript, the tool calls with their
216
- * approvals, a composer, and a stop button while a run is under way. The
241
+ * approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
217
242
  * session is the app's own, so the chat opens no second connection; until it
218
243
  * has one the thread is empty and the composer is disabled. It exists for the
219
244
  * first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
220
245
  * and builds the rest from assistant-ui's primitives.
221
246
  */
222
- export function SubakoChat({ session, className, components, nonce, children }) {
247
+ export function SubakoChat({ session, className, components, nonce, widgets, children }) {
223
248
  const runtime = useSubakoRuntime(session);
224
249
  const state = useSessionState(session);
225
- return (_jsxs(AssistantRuntimeProvider, { runtime: runtime, children: [_jsx("style", { nonce: nonce, children: STYLE }), children, _jsx(PartsContext.Provider, { value: components, children: _jsxs(ThreadPrimitive.Root, { className: className === undefined ? "subako-chat" : `subako-chat ${className}`, children: [_jsx(ThreadPrimitive.Viewport, { className: "subako-viewport", children: _jsx(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } }) }), _jsxs(ComposerPrimitive.Root, { className: "subako-composer", children: [_jsx(ComposerPrimitive.Input, { className: "subako-input", rows: 1, placeholder: "Message the agent" }), _jsx(ComposerPrimitive.Send, { children: "Send" }), state?.isRunning === true ? _jsx(ComposerPrimitive.Cancel, { children: "Stop" }) : null] })] }) })] }));
250
+ return (_jsxs(AssistantRuntimeProvider, { runtime: runtime, children: [_jsx("style", { nonce: nonce, children: STYLE }), children, _jsx(PartsContext.Provider, { value: components, children: _jsxs(ThreadPrimitive.Root, { className: className === undefined ? "subako-chat" : `subako-chat ${className}`, children: [_jsx(ThreadPrimitive.Viewport, { className: "subako-viewport", children: _jsx(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } }) }), session === null || state === null || state.inputs.length === 0 ? null : (_jsx("div", { className: "subako-inputs", children: state.inputs.map((input) => (_jsx(SubakoInputForm, { input: input, widgets: widgets, onAnswer: (answer) => session.answerInput(input.call_id, answer) }, `${input.call_id}:${input.seq}`))) })), _jsxs(ComposerPrimitive.Root, { className: "subako-composer", children: [_jsx(ComposerPrimitive.Input, { className: "subako-input", rows: 1, placeholder: "Message the agent" }), _jsx(ComposerPrimitive.Send, { children: "Send" }), state?.isRunning === true ? _jsx(ComposerPrimitive.Cancel, { children: "Stop" }) : null] })] }) })] }));
226
251
  }
227
252
  //# sourceMappingURL=chat.js.map
package/dist/chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.tsx"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
2
+ export { SubakoInputForm, type SubakoInputFormProps, type WidgetProps, type WidgetRenderer, type WidgetRenderers, } from "./input-form.js";
2
3
  export { convertLiveMessage, convertMessage } from "./convert.js";
3
4
  export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { SubakoChat, SubakoToolApproval } from "./chat.js";
2
+ export { SubakoInputForm, } from "./input-form.js";
2
3
  export { convertLiveMessage, convertMessage } from "./convert.js";
3
4
  export { useSubakoRuntime } from "./runtime.js";
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,kBAAkB,EAAgC,MAAM,WAAW,CAAC;AAC/G,OAAO,EACN,eAAe,GAKf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { InputAnswer, PendingInput, QuestionBody } from "@subako-ai/sdk";
2
+ import type { ReactNode } from "react";
3
+ /** What a custom widget's renderer is handed: the agent's props, and the answer so far. */
4
+ export interface WidgetProps {
5
+ /** What the agent filled in, as the widget's props schema describes it. */
6
+ readonly props: unknown;
7
+ /** The answer so far: `undefined` until the person gives one. */
8
+ readonly value: unknown;
9
+ /** Sets the answer, which must match the widget's answer schema; `undefined` clears it. */
10
+ readonly onChange: (value: unknown) => void;
11
+ readonly question: QuestionBody;
12
+ }
13
+ /** A renderer for one custom widget, registered under the widget's id. */
14
+ export type WidgetRenderer = (props: WidgetProps) => ReactNode;
15
+ /** The custom widgets a client draws, by widget id. An id with no renderer answers `unsupported`. */
16
+ export type WidgetRenderers = Readonly<Record<string, WidgetRenderer>>;
17
+ export interface SubakoInputFormProps {
18
+ readonly input: PendingInput;
19
+ readonly widgets?: WidgetRenderers;
20
+ /** Sends the answer: `session.answerInput(input.call_id, answer)`. */
21
+ readonly onAnswer: (answer: InputAnswer) => Promise<void>;
22
+ }
23
+ /**
24
+ * One form an agent put through `builtin:ask_user`: each question drawn with
25
+ * its widget, a Submit that sends one outcome per question, and a Dismiss.
26
+ * A custom widget draws with the renderer `widgets` registers under its id; a
27
+ * question this client cannot draw is sent as `unsupported`, so the agent can
28
+ * ask it another way. A form none of whose questions can be drawn shows only
29
+ * a button that tells the agent so.
30
+ */
31
+ export declare function SubakoInputForm({ input, widgets, onAnswer }: SubakoInputFormProps): ReactNode;
32
+ //# sourceMappingURL=input-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-form.d.ts","sourceRoot":"","sources":["../src/input-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAmC,MAAM,gBAAgB,CAAC;AAE/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CAChC;AAED,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAC;AAE/D,qGAAqG;AACrG,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AA2ID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,OAAY,EAAE,QAAQ,EAAE,EAAE,oBAAoB,GAAG,SAAS,CAiFlG"}
@@ -0,0 +1,101 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { readWidget, submittedAnswer, unsupportedAnswer, widgetName } from "@subako-ai/sdk";
4
+ import { useState } from "react";
5
+ /** Whether this client can draw `widget`. */
6
+ const drawable = (widget, widgets) => {
7
+ switch (widget.type) {
8
+ case "select":
9
+ case "text":
10
+ case "confirm":
11
+ return true;
12
+ case "custom":
13
+ return widgets[widget.data.id] !== undefined;
14
+ case "unsupported":
15
+ return false;
16
+ default:
17
+ return widget;
18
+ }
19
+ };
20
+ /** Whether `value` is an answer at all, rather than a blank. */
21
+ const given = (value) => value !== undefined && value !== "" && !(Array.isArray(value) && value.length === 0);
22
+ /** The question's outcome on submit: drawn and answered, left blank, or not drawable here. */
23
+ function outcomeOf(canDraw, value) {
24
+ if (!canDraw) {
25
+ return { type: "unsupported" };
26
+ }
27
+ return given(value) ? { type: "answered", data: { value } } : { type: "skipped" };
28
+ }
29
+ /** A select: radios for one pick, checkboxes for several, and a text box for the person's own answer. */
30
+ function SelectField({ question, widget, value, onChange, }) {
31
+ const picked = Array.isArray(value) ? value : [];
32
+ const labels = widget.data.options.map((option) => option.label);
33
+ const other = picked.find((label) => !labels.includes(label)) ?? "";
34
+ const chosen = picked.filter((label) => labels.includes(label));
35
+ const set = (next, otherText) => onChange(otherText === "" ? [...next] : [...next, otherText]);
36
+ return (_jsxs("div", { className: "subako-input-options", role: widget.data.multiple ? "group" : "radiogroup", children: [widget.data.options.map((option) => (_jsxs("label", { className: "subako-input-option", children: [_jsx("input", { type: widget.data.multiple ? "checkbox" : "radio", name: question.id, checked: chosen.includes(option.label), onChange: (event) => {
37
+ if (widget.data.multiple) {
38
+ set(event.target.checked ? [...chosen, option.label] : chosen.filter((label) => label !== option.label), other);
39
+ }
40
+ else {
41
+ set([option.label], "");
42
+ }
43
+ } }), _jsx("span", { children: option.label }), option.description === undefined ? null : _jsx("span", { className: "subako-input-hint", children: option.description })] }, option.label))), widget.data.allow_other ? (_jsx("input", { className: "subako-input-text", "aria-label": "Other", placeholder: "Other", value: other, onChange: (event) => set(widget.data.multiple ? chosen : [], event.target.value) })) : null] }));
44
+ }
45
+ function Field(props) {
46
+ const { question, widget, widgets, value, onChange } = props;
47
+ switch (widget.type) {
48
+ case "select":
49
+ return _jsx(SelectField, { ...props, widget: widget });
50
+ case "text":
51
+ return widget.data.multiline ? (_jsx("textarea", { className: "subako-input-text", "aria-label": question.prompt, rows: 3, value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) })) : (_jsx("input", { className: "subako-input-text", "aria-label": question.prompt, value: typeof value === "string" ? value : "", onChange: (event) => onChange(event.target.value) }));
52
+ case "confirm":
53
+ return (_jsx("div", { className: "subako-input-options", role: "radiogroup", children: [true, false].map((answer) => (_jsxs("label", { className: "subako-input-option", children: [_jsx("input", { type: "radio", name: question.id, checked: value === answer, onChange: () => onChange(answer) }), _jsx("span", { children: answer ? "Yes" : "No" })] }, String(answer)))) }));
54
+ case "custom": {
55
+ const Render = widgets[widget.data.id];
56
+ return Render === undefined ? (_jsx(Unsupported, { widget: widget })) : (_jsx(Render, { props: widget.data.props, value: value, onChange: onChange, question: question }));
57
+ }
58
+ case "unsupported":
59
+ return _jsx(Unsupported, { widget: widget });
60
+ default:
61
+ return widget;
62
+ }
63
+ }
64
+ function Unsupported({ widget }) {
65
+ return _jsxs("div", { className: "subako-input-hint", children: ["This app can\u2019t show \u201C", widgetName(widget), "\u201D."] });
66
+ }
67
+ /**
68
+ * One form an agent put through `builtin:ask_user`: each question drawn with
69
+ * its widget, a Submit that sends one outcome per question, and a Dismiss.
70
+ * A custom widget draws with the renderer `widgets` registers under its id; a
71
+ * question this client cannot draw is sent as `unsupported`, so the agent can
72
+ * ask it another way. A form none of whose questions can be drawn shows only
73
+ * a button that tells the agent so.
74
+ */
75
+ export function SubakoInputForm({ input, widgets = {}, onAnswer }) {
76
+ const [values, setValues] = useState({});
77
+ const [sending, setSending] = useState(false);
78
+ const [error, setError] = useState(undefined);
79
+ const questions = input.request.questions.map((question) => {
80
+ const widget = readWidget(question.widget);
81
+ return { question, widget, canDraw: drawable(widget, widgets) };
82
+ });
83
+ const send = (answer) => {
84
+ setSending(true);
85
+ setError(undefined);
86
+ onAnswer(answer).then(() => setSending(false), (cause) => {
87
+ setSending(false);
88
+ setError(cause instanceof Error ? cause.message : String(cause));
89
+ });
90
+ };
91
+ const failure = error === undefined ? null : _jsx("div", { className: "subako-input-error", children: error });
92
+ if (questions.every(({ canDraw }) => !canDraw)) {
93
+ return (_jsxs("div", { className: "subako-input-form", "data-testid": `input-${input.call_id}`, children: [_jsx("div", { className: "subako-input-hint", children: "This app can\u2019t show the agent\u2019s question." }), _jsx("div", { className: "subako-input-actions", children: _jsx("button", { type: "button", disabled: sending, onClick: () => send(unsupportedAnswer(input)), children: "Tell the agent" }) }), failure] }));
94
+ }
95
+ const missing = questions.some(({ question, canDraw }) => question.required && canDraw && !given(values[question.id]));
96
+ return (_jsxs("form", { className: "subako-input-form", "data-testid": `input-${input.call_id}`, onSubmit: (event) => {
97
+ event.preventDefault();
98
+ send(submittedAnswer(input, Object.fromEntries(questions.map(({ question, canDraw }) => [question.id, outcomeOf(canDraw, values[question.id])]))));
99
+ }, children: [questions.map(({ question, widget }) => (_jsxs("fieldset", { className: "subako-input-question", children: [_jsxs("legend", { className: "subako-input-prompt", children: [question.header === undefined ? null : _jsxs("strong", { children: [question.header, ": "] }), question.prompt, question.required ? " *" : null] }), _jsx(Field, { question: question, widget: widget, widgets: widgets, value: values[question.id], onChange: (value) => setValues((previous) => ({ ...previous, [question.id]: value })) })] }, question.id))), _jsxs("div", { className: "subako-input-actions", children: [_jsx("button", { type: "submit", disabled: sending || missing, children: "Submit" }), _jsx("button", { type: "button", disabled: sending, onClick: () => send({ type: "declined", data: {} }), children: "Dismiss" })] }), failure] }));
100
+ }
101
+ //# sourceMappingURL=input-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-form.js","sourceRoot":"","sources":["../src/input-form.tsx"],"names":[],"mappings":"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"}
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AssistantRuntime } from "@assistant-ui/react";
2
- import type { SessionState } from "@subako-ai/sdk";
2
+ import type { InputAnswer, SessionState } from "@subako-ai/sdk";
3
3
  /**
4
4
  * What the runtime reads off a session: the SDK's `SessionConnection`, or
5
5
  * anything else that carries the same members. It is spelled structurally so a
@@ -15,6 +15,8 @@ export interface SubakoSession {
15
15
  /** Cancels the active run. */
16
16
  cancel(): Promise<void>;
17
17
  resolveApproval(callId: string, decision: "allow" | "deny", message?: string): Promise<void>;
18
+ /** Answers a form the agent put through `builtin:ask_user`. */
19
+ answerInput(callId: string, answer: InputAnswer): Promise<void>;
18
20
  }
19
21
  /**
20
22
  * An assistant-ui runtime over one Subako session: the log is the store, and
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,EAAe,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AAInF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,sEAAsE;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,8BAA8B;IAC9B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7F;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,gBAAgB,CA+BhF"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,KAAK,EAAE,WAAW,EAAe,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AAIhG;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,sEAAsE;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,8BAA8B;IAC9B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7F,+DAA+D;IAC/D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,gBAAgB,CA+BhF"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAsBlE,iGAAiG;AACjG,MAAM,OAAO,GAA2B,EAAE,CAAC;AAE3C,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAErH,2EAA2E;AAC3E,SAAS,YAAY,CAAC,OAAsB;IAC3C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC7D,kFAAkF;IAClF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC;IACzB,yFAAyF;IACzF,MAAM,QAAQ,GAAG,OAAO,CACvB,GAA2B,EAAE,CAC5B,UAAU,KAAK,SAAS;QACvB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YACxC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,EAC7B,CAAC,UAAU,EAAE,IAAI,CAAC,CAClB,CAAC;IACF,OAAO,uBAAuB,CAAc;QAC3C,QAAQ;QACR,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,KAAK;QACpC,UAAU,EAAE,OAAO,KAAK,IAAI;QAC5B,cAAc,EAAE,YAAY;QAC5B,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,QAAQ,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,EAAE,MAAM,EAAE,CAAC;QACzB,CAAC;QACD,gFAAgF;QAChF,uBAAuB,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;YACnE,MAAM,OAAO,EAAE,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAwBlE,iGAAiG;AACjG,MAAM,OAAO,GAA2B,EAAE,CAAC;AAE3C,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAErH,2EAA2E;AAC3E,SAAS,YAAY,CAAC,OAAsB;IAC3C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA6B;IAC7D,kFAAkF;IAClF,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC;IACzB,yFAAyF;IACzF,MAAM,QAAQ,GAAG,OAAO,CACvB,GAA2B,EAAE,CAC5B,UAAU,KAAK,SAAS;QACvB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YACxC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,EAC7B,CAAC,UAAU,EAAE,IAAI,CAAC,CAClB,CAAC;IACF,OAAO,uBAAuB,CAAc;QAC3C,QAAQ;QACR,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,KAAK;QACpC,UAAU,EAAE,OAAO,KAAK,IAAI;QAC5B,cAAc,EAAE,YAAY;QAC5B,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,QAAQ,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,EAAE,MAAM,EAAE,CAAC;QACzB,CAAC;QACD,gFAAgF;QAChF,uBAAuB,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;YACnE,MAAM,OAAO,EAAE,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjF,CAAC;KACD,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subako-ai/assistant-ui",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "assistant-ui runtime and chat for Subako sessions",
5
5
  "homepage": "https://subako.ai",
6
6
  "license": "Apache-2.0",
@@ -39,15 +39,15 @@
39
39
  "react": "^19.2.0",
40
40
  "react-dom": "^19.2.0",
41
41
  "typescript": "^7",
42
- "@subako-ai/sdk": "0.2.2",
43
- "@subako-ai/react": "0.2.2"
42
+ "@subako-ai/sdk": "0.2.4",
43
+ "@subako-ai/react": "0.2.4"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@assistant-ui/react": "^0.15.0",
47
47
  "@assistant-ui/react-markdown": "^0.14.15",
48
48
  "react": "^19.0.0",
49
- "@subako-ai/react": "^0.2.2",
50
- "@subako-ai/sdk": "^0.2.2"
49
+ "@subako-ai/react": "^0.2.4",
50
+ "@subako-ai/sdk": "^0.2.4"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=22"
package/src/chat.tsx CHANGED
@@ -13,6 +13,7 @@ import { useSessionState } from "@subako-ai/react";
13
13
  import type { ComponentProps, ReactNode } from "react";
14
14
  import { createContext, useContext } from "react";
15
15
  import remarkGfm from "remark-gfm";
16
+ import { SubakoInputForm, type WidgetRenderers } from "./input-form.js";
16
17
  import type { SubakoSession } from "./runtime.js";
17
18
  import { useSubakoRuntime } from "./runtime.js";
18
19
 
@@ -99,6 +100,23 @@ const STYLE = `
99
100
  border-radius: 6px; background: var(--subako-surface); color: var(--subako-fg); }
100
101
  .subako-approval button { padding: 2px 10px; border: 1px solid var(--subako-border); border-radius: 6px;
101
102
  background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
103
+ .subako-inputs { display: flex; flex-direction: column; gap: 8px; padding: 0 8px; }
104
+ .subako-input-form { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 8px 10px;
105
+ border: 1px solid var(--subako-border); border-radius: 8px; background: var(--subako-surface);
106
+ color: var(--subako-fg); }
107
+ .subako-input-question { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; border: 0;
108
+ min-width: 0; }
109
+ .subako-input-prompt { padding: 0; font-weight: 600; }
110
+ .subako-input-options { display: flex; flex-direction: column; gap: 2px; }
111
+ .subako-input-option { display: flex; align-items: baseline; gap: 6px; }
112
+ .subako-input-hint { color: var(--subako-muted); font-size: 12px; }
113
+ .subako-input-text { padding: 4px 6px; border: 1px solid var(--subako-border); border-radius: 6px;
114
+ background: var(--subako-bg); color: var(--subako-fg); font: inherit; }
115
+ .subako-input-actions { display: flex; gap: 8px; }
116
+ .subako-input-actions button { padding: 2px 12px; border: 1px solid var(--subako-border); border-radius: 6px;
117
+ background: var(--subako-bg); color: var(--subako-fg); font: inherit; cursor: pointer; }
118
+ .subako-input-actions button:disabled { opacity: 0.5; cursor: default; }
119
+ .subako-input-error { color: var(--subako-error); font-size: 12px; }
102
120
  .subako-composer { display: flex; gap: 8px; padding: 8px; border-top: 1px solid var(--subako-border);
103
121
  background: var(--subako-bg); color: var(--subako-fg); }
104
122
  .subako-input { flex: 1 1 auto; resize: none; padding: 6px 8px; border: 1px solid var(--subako-border);
@@ -121,7 +139,14 @@ div:where(.subako-chat) details:where(.subako-tool-fold),
121
139
  div:where(.subako-chat) summary:where(.subako-tool-summary),
122
140
  div:where(.subako-chat) span:where(.subako-tool-name, .subako-tool-status, .subako-writing),
123
141
  div:where(.subako-chat) pre:where(.subako-tool-args, .subako-tool-result),
124
- div:where(.subako-chat) :where(.subako-approval, .subako-composer) > button,
142
+ div:where(.subako-chat) :where(.subako-approval, .subako-composer, .subako-input-actions) > button,
143
+ div:where(.subako-chat) div:where(.subako-inputs, .subako-input-options, .subako-input-hint, .subako-input-actions,
144
+ .subako-input-error),
145
+ div:where(.subako-chat) :where(.subako-input-form):is(form, div),
146
+ div:where(.subako-chat) fieldset:where(.subako-input-question),
147
+ div:where(.subako-chat) legend:where(.subako-input-prompt),
148
+ div:where(.subako-chat) label:where(.subako-input-option),
149
+ div:where(.subako-chat) :where(.subako-input-text):is(input, textarea),
125
150
  div:where(.subako-chat) form:where(.subako-composer),
126
151
  div:where(.subako-chat) textarea:where(.subako-input),
127
152
  div:where(.subako-chat) textarea:where(.subako-input)::placeholder { all: revert-layer; }
@@ -315,19 +340,25 @@ export interface SubakoChatProps {
315
340
  * unstyled; a page with no such policy needs nothing here.
316
341
  */
317
342
  readonly nonce?: string;
343
+ /**
344
+ * The custom widgets this app draws in a form the agent puts through
345
+ * `builtin:ask_user`, by widget id. A widget with no renderer here lets the
346
+ * person tell the agent the app cannot show it.
347
+ */
348
+ readonly widgets?: WidgetRenderers;
318
349
  /** Rendered inside the runtime, which is where a tool UI registers itself. */
319
350
  readonly children?: ReactNode;
320
351
  }
321
352
 
322
353
  /**
323
354
  * One session as a working chat: the transcript, the tool calls with their
324
- * approvals, a composer, and a stop button while a run is under way. The
355
+ * approvals, the forms the agent puts to the person, a composer, and a stop button while a run is under way. The
325
356
  * session is the app's own, so the chat opens no second connection; until it
326
357
  * has one the thread is empty and the composer is disabled. It exists for the
327
358
  * first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
328
359
  * and builds the rest from assistant-ui's primitives.
329
360
  */
330
- export function SubakoChat({ session, className, components, nonce, children }: SubakoChatProps): ReactNode {
361
+ export function SubakoChat({ session, className, components, nonce, widgets, children }: SubakoChatProps): ReactNode {
331
362
  const runtime = useSubakoRuntime(session);
332
363
  const state = useSessionState(session);
333
364
  return (
@@ -339,6 +370,18 @@ export function SubakoChat({ session, className, components, nonce, children }:
339
370
  <ThreadPrimitive.Viewport className="subako-viewport">
340
371
  <ThreadPrimitive.Messages components={{ UserMessage, AssistantMessage }} />
341
372
  </ThreadPrimitive.Viewport>
373
+ {session === null || state === null || state.inputs.length === 0 ? null : (
374
+ <div className="subako-inputs">
375
+ {state.inputs.map((input) => (
376
+ <SubakoInputForm
377
+ key={`${input.call_id}:${input.seq}`}
378
+ input={input}
379
+ widgets={widgets}
380
+ onAnswer={(answer) => session.answerInput(input.call_id, answer)}
381
+ />
382
+ ))}
383
+ </div>
384
+ )}
342
385
  <ComposerPrimitive.Root className="subako-composer">
343
386
  <ComposerPrimitive.Input className="subako-input" rows={1} placeholder="Message the agent" />
344
387
  <ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
package/src/index.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  export { SubakoChat, type SubakoChatProps, SubakoToolApproval, type SubakoToolApprovalProps } from "./chat.js";
2
+ export {
3
+ SubakoInputForm,
4
+ type SubakoInputFormProps,
5
+ type WidgetProps,
6
+ type WidgetRenderer,
7
+ type WidgetRenderers,
8
+ } from "./input-form.js";
2
9
  export { convertLiveMessage, convertMessage } from "./convert.js";
3
10
  export { type SubakoSession, useSubakoRuntime } from "./runtime.js";
@@ -0,0 +1,258 @@
1
+ "use client";
2
+
3
+ import type { InputAnswer, PendingInput, QuestionBody, QuestionOutcomeBody, ReadWidget } from "@subako-ai/sdk";
4
+ import { readWidget, submittedAnswer, unsupportedAnswer, widgetName } from "@subako-ai/sdk";
5
+ import type { ReactNode } from "react";
6
+ import { useState } from "react";
7
+
8
+ /** What a custom widget's renderer is handed: the agent's props, and the answer so far. */
9
+ export interface WidgetProps {
10
+ /** What the agent filled in, as the widget's props schema describes it. */
11
+ readonly props: unknown;
12
+ /** The answer so far: `undefined` until the person gives one. */
13
+ readonly value: unknown;
14
+ /** Sets the answer, which must match the widget's answer schema; `undefined` clears it. */
15
+ readonly onChange: (value: unknown) => void;
16
+ readonly question: QuestionBody;
17
+ }
18
+
19
+ /** A renderer for one custom widget, registered under the widget's id. */
20
+ export type WidgetRenderer = (props: WidgetProps) => ReactNode;
21
+
22
+ /** The custom widgets a client draws, by widget id. An id with no renderer answers `unsupported`. */
23
+ export type WidgetRenderers = Readonly<Record<string, WidgetRenderer>>;
24
+
25
+ export interface SubakoInputFormProps {
26
+ readonly input: PendingInput;
27
+ readonly widgets?: WidgetRenderers;
28
+ /** Sends the answer: `session.answerInput(input.call_id, answer)`. */
29
+ readonly onAnswer: (answer: InputAnswer) => Promise<void>;
30
+ }
31
+
32
+ /** Whether this client can draw `widget`. */
33
+ const drawable = (widget: ReadWidget, widgets: WidgetRenderers): boolean => {
34
+ switch (widget.type) {
35
+ case "select":
36
+ case "text":
37
+ case "confirm":
38
+ return true;
39
+ case "custom":
40
+ return widgets[widget.data.id] !== undefined;
41
+ case "unsupported":
42
+ return false;
43
+ default:
44
+ return widget satisfies never;
45
+ }
46
+ };
47
+
48
+ /** Whether `value` is an answer at all, rather than a blank. */
49
+ const given = (value: unknown): boolean =>
50
+ value !== undefined && value !== "" && !(Array.isArray(value) && value.length === 0);
51
+
52
+ /** The question's outcome on submit: drawn and answered, left blank, or not drawable here. */
53
+ function outcomeOf(canDraw: boolean, value: unknown): QuestionOutcomeBody {
54
+ if (!canDraw) {
55
+ return { type: "unsupported" };
56
+ }
57
+ return given(value) ? { type: "answered", data: { value } } : { type: "skipped" };
58
+ }
59
+
60
+ interface FieldProps {
61
+ readonly question: QuestionBody;
62
+ readonly widget: ReadWidget;
63
+ readonly widgets: WidgetRenderers;
64
+ readonly value: unknown;
65
+ readonly onChange: (value: unknown) => void;
66
+ }
67
+
68
+ /** A select: radios for one pick, checkboxes for several, and a text box for the person's own answer. */
69
+ function SelectField({
70
+ question,
71
+ widget,
72
+ value,
73
+ onChange,
74
+ }: FieldProps & { readonly widget: Extract<ReadWidget, { type: "select" }> }): ReactNode {
75
+ const picked = Array.isArray(value) ? (value as string[]) : [];
76
+ const labels = widget.data.options.map((option) => option.label);
77
+ const other = picked.find((label) => !labels.includes(label)) ?? "";
78
+ const chosen = picked.filter((label) => labels.includes(label));
79
+ const set = (next: readonly string[], otherText: string) =>
80
+ onChange(otherText === "" ? [...next] : [...next, otherText]);
81
+ return (
82
+ <div className="subako-input-options" role={widget.data.multiple ? "group" : "radiogroup"}>
83
+ {widget.data.options.map((option) => (
84
+ <label key={option.label} className="subako-input-option">
85
+ <input
86
+ type={widget.data.multiple ? "checkbox" : "radio"}
87
+ name={question.id}
88
+ checked={chosen.includes(option.label)}
89
+ onChange={(event) => {
90
+ if (widget.data.multiple) {
91
+ set(
92
+ event.target.checked ? [...chosen, option.label] : chosen.filter((label) => label !== option.label),
93
+ other,
94
+ );
95
+ } else {
96
+ set([option.label], "");
97
+ }
98
+ }}
99
+ />
100
+ <span>{option.label}</span>
101
+ {option.description === undefined ? null : <span className="subako-input-hint">{option.description}</span>}
102
+ </label>
103
+ ))}
104
+ {widget.data.allow_other ? (
105
+ <input
106
+ className="subako-input-text"
107
+ aria-label="Other"
108
+ placeholder="Other"
109
+ value={other}
110
+ onChange={(event) => set(widget.data.multiple ? chosen : [], event.target.value)}
111
+ />
112
+ ) : null}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ function Field(props: FieldProps): ReactNode {
118
+ const { question, widget, widgets, value, onChange } = props;
119
+ switch (widget.type) {
120
+ case "select":
121
+ return <SelectField {...props} widget={widget} />;
122
+ case "text":
123
+ return widget.data.multiline ? (
124
+ <textarea
125
+ className="subako-input-text"
126
+ aria-label={question.prompt}
127
+ rows={3}
128
+ value={typeof value === "string" ? value : ""}
129
+ onChange={(event) => onChange(event.target.value)}
130
+ />
131
+ ) : (
132
+ <input
133
+ className="subako-input-text"
134
+ aria-label={question.prompt}
135
+ value={typeof value === "string" ? value : ""}
136
+ onChange={(event) => onChange(event.target.value)}
137
+ />
138
+ );
139
+ case "confirm":
140
+ return (
141
+ <div className="subako-input-options" role="radiogroup">
142
+ {[true, false].map((answer) => (
143
+ <label key={String(answer)} className="subako-input-option">
144
+ <input type="radio" name={question.id} checked={value === answer} onChange={() => onChange(answer)} />
145
+ <span>{answer ? "Yes" : "No"}</span>
146
+ </label>
147
+ ))}
148
+ </div>
149
+ );
150
+ case "custom": {
151
+ const Render = widgets[widget.data.id];
152
+ return Render === undefined ? (
153
+ <Unsupported widget={widget} />
154
+ ) : (
155
+ <Render props={widget.data.props} value={value} onChange={onChange} question={question} />
156
+ );
157
+ }
158
+ case "unsupported":
159
+ return <Unsupported widget={widget} />;
160
+ default:
161
+ return widget satisfies never;
162
+ }
163
+ }
164
+
165
+ function Unsupported({ widget }: { readonly widget: ReadWidget }): ReactNode {
166
+ return <div className="subako-input-hint">This app can’t show “{widgetName(widget)}”.</div>;
167
+ }
168
+
169
+ /**
170
+ * One form an agent put through `builtin:ask_user`: each question drawn with
171
+ * its widget, a Submit that sends one outcome per question, and a Dismiss.
172
+ * A custom widget draws with the renderer `widgets` registers under its id; a
173
+ * question this client cannot draw is sent as `unsupported`, so the agent can
174
+ * ask it another way. A form none of whose questions can be drawn shows only
175
+ * a button that tells the agent so.
176
+ */
177
+ export function SubakoInputForm({ input, widgets = {}, onAnswer }: SubakoInputFormProps): ReactNode {
178
+ const [values, setValues] = useState<Readonly<Record<string, unknown>>>({});
179
+ const [sending, setSending] = useState(false);
180
+ const [error, setError] = useState<string | undefined>(undefined);
181
+ const questions = input.request.questions.map((question) => {
182
+ const widget = readWidget(question.widget);
183
+ return { question, widget, canDraw: drawable(widget, widgets) };
184
+ });
185
+ const send = (answer: InputAnswer) => {
186
+ setSending(true);
187
+ setError(undefined);
188
+ onAnswer(answer).then(
189
+ () => setSending(false),
190
+ (cause: unknown) => {
191
+ setSending(false);
192
+ setError(cause instanceof Error ? cause.message : String(cause));
193
+ },
194
+ );
195
+ };
196
+ const failure = error === undefined ? null : <div className="subako-input-error">{error}</div>;
197
+
198
+ if (questions.every(({ canDraw }) => !canDraw)) {
199
+ return (
200
+ <div className="subako-input-form" data-testid={`input-${input.call_id}`}>
201
+ <div className="subako-input-hint">This app can’t show the agent’s question.</div>
202
+ <div className="subako-input-actions">
203
+ <button type="button" disabled={sending} onClick={() => send(unsupportedAnswer(input))}>
204
+ Tell the agent
205
+ </button>
206
+ </div>
207
+ {failure}
208
+ </div>
209
+ );
210
+ }
211
+
212
+ const missing = questions.some(
213
+ ({ question, canDraw }) => question.required && canDraw && !given(values[question.id]),
214
+ );
215
+ return (
216
+ <form
217
+ className="subako-input-form"
218
+ data-testid={`input-${input.call_id}`}
219
+ onSubmit={(event) => {
220
+ event.preventDefault();
221
+ send(
222
+ submittedAnswer(
223
+ input,
224
+ Object.fromEntries(
225
+ questions.map(({ question, canDraw }) => [question.id, outcomeOf(canDraw, values[question.id])]),
226
+ ),
227
+ ),
228
+ );
229
+ }}
230
+ >
231
+ {questions.map(({ question, widget }) => (
232
+ <fieldset key={question.id} className="subako-input-question">
233
+ <legend className="subako-input-prompt">
234
+ {question.header === undefined ? null : <strong>{question.header}: </strong>}
235
+ {question.prompt}
236
+ {question.required ? " *" : null}
237
+ </legend>
238
+ <Field
239
+ question={question}
240
+ widget={widget}
241
+ widgets={widgets}
242
+ value={values[question.id]}
243
+ onChange={(value) => setValues((previous) => ({ ...previous, [question.id]: value }))}
244
+ />
245
+ </fieldset>
246
+ ))}
247
+ <div className="subako-input-actions">
248
+ <button type="submit" disabled={sending || missing}>
249
+ Submit
250
+ </button>
251
+ <button type="button" disabled={sending} onClick={() => send({ type: "declined", data: {} })}>
252
+ Dismiss
253
+ </button>
254
+ </div>
255
+ {failure}
256
+ </form>
257
+ );
258
+ }
package/src/runtime.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import type { AppendMessage, AssistantRuntime } from "@assistant-ui/react";
4
4
  import { useExternalStoreRuntime } from "@assistant-ui/react";
5
5
  import { useSessionState } from "@subako-ai/react";
6
- import type { LiveMessage, SessionState, TranscriptMessage } from "@subako-ai/sdk";
6
+ import type { InputAnswer, LiveMessage, SessionState, TranscriptMessage } from "@subako-ai/sdk";
7
7
  import { useMemo } from "react";
8
8
  import { convertLiveMessage, convertMessage } from "./convert.js";
9
9
 
@@ -22,6 +22,8 @@ export interface SubakoSession {
22
22
  /** Cancels the active run. */
23
23
  cancel(): Promise<void>;
24
24
  resolveApproval(callId: string, decision: "allow" | "deny", message?: string): Promise<void>;
25
+ /** Answers a form the agent put through `builtin:ask_user`. */
26
+ answerInput(callId: string, answer: InputAnswer): Promise<void>;
25
27
  }
26
28
 
27
29
  /** One message of the thread: one the log holds, or one a model call is still producing. */