@subako-ai/assistant-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +300 -0
- package/dist/chat.d.ts +49 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +227 -0
- package/dist/chat.js.map +1 -0
- package/dist/convert.d.ts +14 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +69 -0
- package/dist/convert.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +41 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +58 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +59 -0
- package/src/chat.tsx +351 -0
- package/src/convert.ts +86 -0
- package/src/index.ts +3 -0
- package/src/runtime.ts +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Kikuvi, Inc.
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# @subako-ai/assistant-ui
|
|
2
|
+
|
|
3
|
+
[assistant-ui](https://www.assistant-ui.com) over a Subako session: one hook
|
|
4
|
+
that turns the session into an assistant-ui runtime, and one component that
|
|
5
|
+
renders a working chat with no setup at all.
|
|
6
|
+
|
|
7
|
+
Requires **React 19**, **`@assistant-ui/react` 0.15** with
|
|
8
|
+
**`@assistant-ui/react-markdown` 0.14**, and **Node 22 or newer** for the
|
|
9
|
+
build. The package is **ESM only**.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @subako-ai/assistant-ui @subako-ai/react @subako-ai/sdk @assistant-ui/react @assistant-ui/react-markdown
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Getting started
|
|
16
|
+
|
|
17
|
+
The provider and the session are `@subako-ai/react`'s; this package is what
|
|
18
|
+
draws them.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { SubakoSessionClient } from "@subako-ai/sdk";
|
|
22
|
+
import { SubakoProvider, useSession } from "@subako-ai/react";
|
|
23
|
+
import { SubakoChat } from "@subako-ai/assistant-ui";
|
|
24
|
+
|
|
25
|
+
const subako = new SubakoSessionClient({
|
|
26
|
+
baseUrl: "https://api.kikuvi.com",
|
|
27
|
+
getToken: (sessionId) => fetch(`/api/subako/token/${sessionId}`).then((r) => r.text()),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function Assistant({ sessionId }: { sessionId: string }) {
|
|
31
|
+
// The app opens the session and hands it down, so the chat opens no second connection.
|
|
32
|
+
return <SubakoChat session={useSession(sessionId)} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function App({ sessionId }: { sessionId: string }) {
|
|
36
|
+
return (
|
|
37
|
+
<SubakoProvider client={subako}>
|
|
38
|
+
<Assistant sessionId={sessionId} />
|
|
39
|
+
</SubakoProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That is the whole chat: the transcript, the tool calls with their results, the
|
|
45
|
+
approval prompt, a composer, and a stop button while a run is under way. The
|
|
46
|
+
assistant's text is drawn as markdown, tables and all, with links that open in
|
|
47
|
+
a new tab; what the person typed is shown as typed. The
|
|
48
|
+
`session` is the one the app opened — `useSession` hands back `null` until its
|
|
49
|
+
effect has connected, which the chat renders as an empty thread with a disabled
|
|
50
|
+
composer — so the tools the app declares and the thread share one connection. It
|
|
51
|
+
brings its own small stylesheet, so it renders acceptably with nothing else
|
|
52
|
+
installed — no CSS framework, no shadcn registry, no `@assistant-ui/styles`.
|
|
53
|
+
`className` lands on the root element for an app that wants to style it, and
|
|
54
|
+
the class names below are stable:
|
|
55
|
+
|
|
56
|
+
| Class | What it is |
|
|
57
|
+
| ------------------------------------------ | --------------------------------------------- |
|
|
58
|
+
| `.subako-chat` | The root. |
|
|
59
|
+
| `.subako-viewport` | The scrolling message list. |
|
|
60
|
+
| `.subako-message`, `-user`, `-assistant` | One bubble. |
|
|
61
|
+
| `.subako-markdown` | The assistant's body, as markdown. |
|
|
62
|
+
| `.subako-thinking` | A `thinking` block. |
|
|
63
|
+
| `.subako-tool` | One tool call, folded to its name and status. |
|
|
64
|
+
| `.subako-tool-args`, `.subako-tool-result` | The input and the output behind the fold. |
|
|
65
|
+
| `.subako-approval` | The allow / deny prompt. |
|
|
66
|
+
| `.subako-composer`, `.subako-input` | The composer. |
|
|
67
|
+
|
|
68
|
+
### Overriding the styles
|
|
69
|
+
|
|
70
|
+
Every rule of the stylesheet sits in a cascade layer named `subako`. A rule
|
|
71
|
+
of the app's own that names one of the classes above wins over it, at any
|
|
72
|
+
specificity and from anywhere in the page:
|
|
73
|
+
|
|
74
|
+
```css
|
|
75
|
+
.subako-user {
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A rule that names no class — a reset's `*` and `button`, the app's own `pre` —
|
|
81
|
+
does not reach the chat's own elements. Each of them carries a guard, in no
|
|
82
|
+
layer, that turns unlayered element rules away and takes the layered ones
|
|
83
|
+
instead, so Tailwind v3's preflight, a `normalize.css`, and the app's global
|
|
84
|
+
`button` all leave the chat as it is. The one rule of a reset's that still
|
|
85
|
+
reaches a button is `[type="button"]`, which names an attribute; it takes the
|
|
86
|
+
background only, and the buttons are the colour of the bar they sit on. A tool
|
|
87
|
+
UI the app draws itself is not guarded: that is the app's own markup.
|
|
88
|
+
|
|
89
|
+
An app whose CSS is layered itself — Tailwind v4 is one — says where `subako`
|
|
90
|
+
goes by naming it in its layer order, ahead of anything else that declares a
|
|
91
|
+
layer. After `base`, so preflight does not strip the chat's own buttons;
|
|
92
|
+
before `components` and `utilities`, so a class the app puts on the root, and
|
|
93
|
+
an override the app writes inside a layer of its own, both win:
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
@layer theme, base, subako, components, utilities;
|
|
97
|
+
@import "tailwindcss";
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Left unnamed, `subako` lands last and the chat's own rules beat the app's
|
|
101
|
+
layered ones.
|
|
102
|
+
|
|
103
|
+
### A page with a Content Security Policy
|
|
104
|
+
|
|
105
|
+
The stylesheet above is an inline `<style>`, so a page whose `style-src-elem`
|
|
106
|
+
names a nonce drops it and the thread renders unstyled. Hand the chat the same
|
|
107
|
+
nonce the page puts on its own tags:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<SubakoChat session={session} nonce={cspNonce} />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A page with no such policy needs nothing: left out, the attribute is not
|
|
114
|
+
written at all.
|
|
115
|
+
|
|
116
|
+
### Colours
|
|
117
|
+
|
|
118
|
+
Every surface names its own colour and its own background, so the chat is
|
|
119
|
+
legible on a page of any colour instead of inheriting half of one. They come
|
|
120
|
+
from eight custom properties, light by default and dark under
|
|
121
|
+
`prefers-color-scheme: dark`; the root also carries `color-scheme: light dark`
|
|
122
|
+
so form controls follow. Set the properties on `.subako-chat` itself to theme
|
|
123
|
+
the chat with the app's own palette — a value set above it is shadowed by the
|
|
124
|
+
chat's own defaults — which is how an app
|
|
125
|
+
whose dark mode is a class or a `data-theme`, rather than the OS setting, keeps
|
|
126
|
+
the chat in step:
|
|
127
|
+
|
|
128
|
+
```css
|
|
129
|
+
.subako-chat {
|
|
130
|
+
--subako-bg: var(--card);
|
|
131
|
+
--subako-fg: var(--ink);
|
|
132
|
+
--subako-muted: var(--dim);
|
|
133
|
+
--subako-accent: var(--accent);
|
|
134
|
+
--subako-accent-fg: #fff;
|
|
135
|
+
--subako-border: var(--line);
|
|
136
|
+
--subako-surface: var(--bg);
|
|
137
|
+
--subako-error: var(--danger);
|
|
138
|
+
color-scheme: inherit;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`--subako-bg` is the panel, the composer and its controls; `--subako-surface`
|
|
143
|
+
is the raised one — the assistant bubble, the tool call's arguments and result,
|
|
144
|
+
the approval bar; `--subako-accent` with `--subako-accent-fg` is the user
|
|
145
|
+
bubble; `--subako-muted` is the reasoning block and the placeholder;
|
|
146
|
+
`--subako-error` is a failed tool result.
|
|
147
|
+
|
|
148
|
+
## `useSubakoRuntime`
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const session = useSession(sessionId);
|
|
152
|
+
const runtime = useSubakoRuntime(session);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`useExternalStoreRuntime` over the session. The log stays the only store: the
|
|
156
|
+
runtime keeps no messages of its own, so a send that fails leaves the thread as
|
|
157
|
+
the server has it. A `null` session — the one `useSession` has yet to open — is
|
|
158
|
+
a thread with no messages and a composer that cannot be typed into.
|
|
159
|
+
|
|
160
|
+
The runtime subscribes to the session itself, with `@subako-ai/react`'s
|
|
161
|
+
`useSessionState`, so the thread follows the log wherever it is mounted. That
|
|
162
|
+
matters because `useSession` only acquires: hand the session to a `<SubakoChat>`
|
|
163
|
+
sitting in some provider's `children` and its props never change, so React would
|
|
164
|
+
never redraw it. Nothing above the chat has to re-render for a new message to
|
|
165
|
+
appear.
|
|
166
|
+
|
|
167
|
+
| assistant-ui | Subako |
|
|
168
|
+
| ------------------------- | -------------------------------------------------------------- |
|
|
169
|
+
| `messages` | `state.transcript`, through `convertMessage` |
|
|
170
|
+
| `isRunning` | `state.isRunning` |
|
|
171
|
+
| `onNew` | `send(text)`, over the text parts of the appended message |
|
|
172
|
+
| `onCancel` | `cancel()` |
|
|
173
|
+
| `onRespondToToolApproval` | `resolveApproval(callId, approved ? "allow" : "deny", reason)` |
|
|
174
|
+
|
|
175
|
+
The argument is anything carrying those members, or `null`, which is what
|
|
176
|
+
`useSession` hands back.
|
|
177
|
+
|
|
178
|
+
### `convertMessage`
|
|
179
|
+
|
|
180
|
+
The pure half, exported so an app can convert a transcript without a runtime:
|
|
181
|
+
|
|
182
|
+
| Log | assistant-ui |
|
|
183
|
+
| ------------------ | ----------------------------------------------------------- |
|
|
184
|
+
| `text` block | a text part |
|
|
185
|
+
| `thinking` block | a reasoning part |
|
|
186
|
+
| `tool_call` block | a tool-call part, with the joined `tool_result` as its text |
|
|
187
|
+
| a pending approval | an approval with no decision, which is what shows a prompt |
|
|
188
|
+
| a settled approval | the same approval, carrying the decision |
|
|
189
|
+
| the event's `seq` | the message id |
|
|
190
|
+
|
|
191
|
+
The conversion is cached on the identity of the message object, not on the id,
|
|
192
|
+
and the connection's `transcript` folds a new event into the messages it already
|
|
193
|
+
made — replacing only the ones that event touched — so every other message is
|
|
194
|
+
converted once and stays converted. A user message carries where it came from as
|
|
195
|
+
`metadata.custom.source`.
|
|
196
|
+
|
|
197
|
+
## Your own thread
|
|
198
|
+
|
|
199
|
+
`<SubakoChat>` exists for the first ten minutes. A real app keeps the runtime
|
|
200
|
+
and builds the thread from assistant-ui's primitives, or from the components
|
|
201
|
+
its shadcn registry installs:
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
function Chat({ sessionId }: { sessionId: string }) {
|
|
205
|
+
const session = useSession(sessionId);
|
|
206
|
+
const runtime = useSubakoRuntime(session);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
210
|
+
<ThreadPrimitive.Root>
|
|
211
|
+
<ThreadPrimitive.Viewport>
|
|
212
|
+
<ThreadPrimitive.Messages components={{ UserMessage, AssistantMessage }} />
|
|
213
|
+
</ThreadPrimitive.Viewport>
|
|
214
|
+
<ComposerPrimitive.Root>
|
|
215
|
+
<ComposerPrimitive.Input />
|
|
216
|
+
<ComposerPrimitive.Send>Send</ComposerPrimitive.Send>
|
|
217
|
+
</ComposerPrimitive.Root>
|
|
218
|
+
</ThreadPrimitive.Root>
|
|
219
|
+
</AssistantRuntimeProvider>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The approval prompt is a tool-call part with an `approval` that carries no
|
|
225
|
+
decision; answer it with the part's own `respondToApproval({ approved })`, which
|
|
226
|
+
is what reaches `resolveApproval`. The stop button is
|
|
227
|
+
`<ComposerPrimitive.Cancel>`, which the runtime wires to `cancel()`.
|
|
228
|
+
|
|
229
|
+
## Tool UI
|
|
230
|
+
|
|
231
|
+
A tool call renders as a row folded to its name and its status — running,
|
|
232
|
+
done, failed, needs approval, denied. Opening the row shows the input and the
|
|
233
|
+
output, laid out as JSON when they are JSON. The approval prompt sits outside
|
|
234
|
+
the fold, so a closed row still asks. To draw one of your tools yourself,
|
|
235
|
+
register a tool UI the ordinary assistant-ui way; it wins over the default row.
|
|
236
|
+
`<SubakoChat>` renders its children inside the runtime, which is where the
|
|
237
|
+
registration has to happen:
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { makeAssistantToolUI } from "@assistant-ui/react";
|
|
241
|
+
|
|
242
|
+
const FillForm = makeAssistantToolUI<{ email?: string }, string>({
|
|
243
|
+
toolName: "client__chat__fill_contact_form",
|
|
244
|
+
render: ({ args, result }) => (result === undefined ? <p>Filling…</p> : <p>Filled {args.email}</p>),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
<SubakoChat session={session}>
|
|
248
|
+
<FillForm />
|
|
249
|
+
</SubakoChat>;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The name is what the model sees the tool under, which for a client tool is
|
|
253
|
+
`client__<client name>__<tool>`.
|
|
254
|
+
|
|
255
|
+
### `components`
|
|
256
|
+
|
|
257
|
+
The same renderers go on `<SubakoChat>` as `components`, which takes what
|
|
258
|
+
`MessagePrimitive.Parts` takes: `tools.by_name` for the tools named,
|
|
259
|
+
`tools.Fallback` for every other one in place of the chat's own row, and
|
|
260
|
+
`Text` for the body in place of the chat's own markdown — for a renderer with
|
|
261
|
+
a syntax highlighter, say, built on `MarkdownTextPrimitive` the way the chat's
|
|
262
|
+
is. The chat's body, its reasoning block and its tool row stay wherever nothing
|
|
263
|
+
is named.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
function FilledForm({ args, result }: ToolCallMessagePartProps) {
|
|
267
|
+
return result === undefined ? <p>Filling…</p> : <p>Filled {String(args.email)}</p>;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
<SubakoChat session={session} components={{ tools: { by_name: { client__chat__fill_contact_form: FilledForm } } }} />;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `SubakoToolApproval`
|
|
274
|
+
|
|
275
|
+
A tool that needs approval has to ask for it from whatever draws it: a row of
|
|
276
|
+
the app's own gets `approval` and `respondToApproval` like any tool-call part,
|
|
277
|
+
and the run waits until something answers. `SubakoToolApproval` is the chat's
|
|
278
|
+
own prompt, for dropping in — Allow and Deny while the server waits on the
|
|
279
|
+
person, nothing once there is an answer or the run was cancelled:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
import { SubakoToolApproval } from "@subako-ai/assistant-ui";
|
|
283
|
+
|
|
284
|
+
function Terse(props: ToolCallMessagePartProps) {
|
|
285
|
+
return (
|
|
286
|
+
<div>
|
|
287
|
+
{props.toolName}
|
|
288
|
+
<SubakoToolApproval {...props} />
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
<SubakoChat session={session} components={{ tools: { Fallback: Terse } }} />;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## What it does not do
|
|
297
|
+
|
|
298
|
+
No thread list, no branching, no editing, no attachments, no speech: the
|
|
299
|
+
session is one conversation, its log is append-only, and the runtime declares
|
|
300
|
+
only what the session can actually do.
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
|
|
2
|
+
import { MessagePrimitive } from "@assistant-ui/react";
|
|
3
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
4
|
+
import type { SubakoSession } from "./runtime.js";
|
|
5
|
+
/** What the approval prompt reads off a tool-call part: the call's name, its approval, and the answer. */
|
|
6
|
+
export type SubakoToolApprovalProps = Pick<ToolCallMessagePartProps, "toolName" | "approval" | "respondToApproval">;
|
|
7
|
+
/**
|
|
8
|
+
* The approval prompt of the chat's own tool row, for a tool UI the app draws
|
|
9
|
+
* itself: Allow and Deny while the server waits on the person, and nothing
|
|
10
|
+
* once there is an answer or the run was cancelled. Spread the tool call's
|
|
11
|
+
* props into it; the answer reaches `resolveApproval` the same way.
|
|
12
|
+
*/
|
|
13
|
+
export declare function SubakoToolApproval({ toolName, approval, respondToApproval }: SubakoToolApprovalProps): ReactNode;
|
|
14
|
+
/** The components an assistant message's parts are drawn with, as `MessagePrimitive.Parts` takes them. */
|
|
15
|
+
type PartComponents = NonNullable<ComponentProps<typeof MessagePrimitive.Parts>["components"]>;
|
|
16
|
+
export interface SubakoChatProps {
|
|
17
|
+
/** The connection from `useSession`, which is `null` until its effect has opened one. */
|
|
18
|
+
readonly session: SubakoSession | null;
|
|
19
|
+
/** Added to the root element, for an app that styles the thread itself. */
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The components an assistant message's parts are drawn with, as
|
|
23
|
+
* `MessagePrimitive.Parts` takes them: `Text` for the body, in place of the
|
|
24
|
+
* chat's own markdown; `tools` for the tool calls, whose `Fallback` replaces
|
|
25
|
+
* the chat's own row and whose `by_name` replaces it for the tools named.
|
|
26
|
+
* The chat's body, reasoning block and tool row stay wherever nothing is
|
|
27
|
+
* named.
|
|
28
|
+
*/
|
|
29
|
+
readonly components?: PartComponents;
|
|
30
|
+
/**
|
|
31
|
+
* The page's CSP nonce, for a page whose `style-src-elem` names one. Without
|
|
32
|
+
* it the browser drops the chat's own stylesheet and the thread renders
|
|
33
|
+
* unstyled; a page with no such policy needs nothing here.
|
|
34
|
+
*/
|
|
35
|
+
readonly nonce?: string;
|
|
36
|
+
/** Rendered inside the runtime, which is where a tool UI registers itself. */
|
|
37
|
+
readonly children?: ReactNode;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 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
|
|
42
|
+
* session is the app's own, so the chat opens no second connection; until it
|
|
43
|
+
* has one the thread is empty and the composer is disabled. It exists for the
|
|
44
|
+
* first ten minutes; an app that wants its own thread keeps `useSubakoRuntime`
|
|
45
|
+
* and builds the rest from assistant-ui's primitives.
|
|
46
|
+
*/
|
|
47
|
+
export declare function SubakoChat({ session, className, components, nonce, children }: SubakoChatProps): ReactNode;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +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"}
|