@talk2view/sdk 0.14.0 → 0.15.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.
Files changed (123) hide show
  1. package/README.md +291 -69
  2. package/dist/assistant-ui/convert.d.ts +40 -0
  3. package/dist/assistant-ui/convert.d.ts.map +1 -0
  4. package/dist/assistant-ui/convert.js +113 -0
  5. package/dist/assistant-ui/convert.js.map +1 -0
  6. package/dist/assistant-ui/dictation.d.ts +39 -0
  7. package/dist/assistant-ui/dictation.d.ts.map +1 -0
  8. package/dist/assistant-ui/dictation.js +128 -0
  9. package/dist/assistant-ui/dictation.js.map +1 -0
  10. package/dist/assistant-ui/index.d.ts +37 -0
  11. package/dist/assistant-ui/index.d.ts.map +1 -0
  12. package/dist/assistant-ui/index.js +224 -0
  13. package/dist/assistant-ui/index.js.map +1 -0
  14. package/dist/auth.d.ts +19 -2
  15. package/dist/auth.d.ts.map +1 -1
  16. package/dist/auth.js +65 -3
  17. package/dist/auth.js.map +1 -1
  18. package/dist/client.d.ts +1 -1
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +4 -4
  21. package/dist/client.js.map +1 -1
  22. package/dist/errors.d.ts +5 -3
  23. package/dist/errors.d.ts.map +1 -1
  24. package/dist/errors.js +28 -6
  25. package/dist/errors.js.map +1 -1
  26. package/dist/index.d.ts +59 -13
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +227 -35
  29. package/dist/index.js.map +1 -1
  30. package/dist/react/T2VProvider.d.ts +2 -2
  31. package/dist/react/T2VProvider.d.ts.map +1 -1
  32. package/dist/react/T2VProvider.js +1 -1
  33. package/dist/react/T2VProvider.js.map +1 -1
  34. package/dist/react/index.d.ts +14 -14
  35. package/dist/react/index.d.ts.map +1 -1
  36. package/dist/react/index.js +7 -7
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/react/usePartnerConfig.d.ts +1 -1
  39. package/dist/react/usePartnerConfig.d.ts.map +1 -1
  40. package/dist/react/usePartnerConfig.js +1 -1
  41. package/dist/react/usePartnerConfig.js.map +1 -1
  42. package/dist/react/useT2VAuth.d.ts +8 -1
  43. package/dist/react/useT2VAuth.d.ts.map +1 -1
  44. package/dist/react/useT2VAuth.js +25 -6
  45. package/dist/react/useT2VAuth.js.map +1 -1
  46. package/dist/react/useT2VChat.d.ts +3 -3
  47. package/dist/react/useT2VChat.d.ts.map +1 -1
  48. package/dist/react/useT2VChat.js +1 -1
  49. package/dist/react/useT2VChat.js.map +1 -1
  50. package/dist/react/useT2VTools.d.ts +1 -1
  51. package/dist/react/useT2VTools.d.ts.map +1 -1
  52. package/dist/react/useT2VTools.js +1 -1
  53. package/dist/react/useT2VTools.js.map +1 -1
  54. package/dist/react/useUserPreferences.d.ts +1 -1
  55. package/dist/react/useUserPreferences.d.ts.map +1 -1
  56. package/dist/react/useUserPreferences.js +1 -1
  57. package/dist/react/useUserPreferences.js.map +1 -1
  58. package/dist/sessions.d.ts +3 -3
  59. package/dist/sessions.d.ts.map +1 -1
  60. package/dist/skills.d.ts +2 -2
  61. package/dist/skills.d.ts.map +1 -1
  62. package/dist/streaming.d.ts +1 -1
  63. package/dist/streaming.d.ts.map +1 -1
  64. package/dist/tools.d.ts +2 -2
  65. package/dist/tools.d.ts.map +1 -1
  66. package/dist/types.d.ts +119 -2
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/ui/components/ApprovalCard.d.ts +1 -1
  69. package/dist/ui/components/ApprovalCard.d.ts.map +1 -1
  70. package/dist/ui/components/ChatHeader.js +1 -1
  71. package/dist/ui/components/ChatHeader.js.map +1 -1
  72. package/dist/ui/components/ChatPanel.d.ts +1 -1
  73. package/dist/ui/components/ChatPanel.d.ts.map +1 -1
  74. package/dist/ui/components/ChatPanel.js +14 -13
  75. package/dist/ui/components/ChatPanel.js.map +1 -1
  76. package/dist/ui/components/ChatWidget.d.ts +1 -1
  77. package/dist/ui/components/ChatWidget.d.ts.map +1 -1
  78. package/dist/ui/components/ChatWidget.js +2 -2
  79. package/dist/ui/components/ChatWidget.js.map +1 -1
  80. package/dist/ui/components/Composer.d.ts.map +1 -1
  81. package/dist/ui/components/Composer.js +13 -6
  82. package/dist/ui/components/Composer.js.map +1 -1
  83. package/dist/ui/components/LoginForm.d.ts.map +1 -1
  84. package/dist/ui/components/LoginForm.js +19 -5
  85. package/dist/ui/components/LoginForm.js.map +1 -1
  86. package/dist/ui/components/MarkdownRenderer.d.ts +4 -0
  87. package/dist/ui/components/MarkdownRenderer.d.ts.map +1 -1
  88. package/dist/ui/components/MarkdownRenderer.js +7 -35
  89. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  90. package/dist/ui/components/MessageActions.js +1 -1
  91. package/dist/ui/components/MessageActions.js.map +1 -1
  92. package/dist/ui/components/MessageBubble.d.ts +1 -1
  93. package/dist/ui/components/MessageBubble.d.ts.map +1 -1
  94. package/dist/ui/components/MessageBubble.js +9 -9
  95. package/dist/ui/components/MessageBubble.js.map +1 -1
  96. package/dist/ui/components/MessageList.js +2 -2
  97. package/dist/ui/components/MessageList.js.map +1 -1
  98. package/dist/ui/components/SettingsPanel.js +4 -4
  99. package/dist/ui/components/SettingsPanel.js.map +1 -1
  100. package/dist/ui/components/Talk2View.d.ts +1 -1
  101. package/dist/ui/components/Talk2View.d.ts.map +1 -1
  102. package/dist/ui/components/Talk2View.js +16 -12
  103. package/dist/ui/components/Talk2View.js.map +1 -1
  104. package/dist/ui/components/ThinkingBlock.js +2 -2
  105. package/dist/ui/components/ThinkingBlock.js.map +1 -1
  106. package/dist/ui/components/WelcomeScreen.js +2 -2
  107. package/dist/ui/components/WelcomeScreen.js.map +1 -1
  108. package/dist/ui/context.d.ts +4 -2
  109. package/dist/ui/context.d.ts.map +1 -1
  110. package/dist/ui/context.js.map +1 -1
  111. package/dist/ui/index.d.ts +27 -26
  112. package/dist/ui/index.d.ts.map +1 -1
  113. package/dist/ui/index.js +21 -20
  114. package/dist/ui/index.js.map +1 -1
  115. package/dist/ui/safeMarkdown.d.ts +17 -0
  116. package/dist/ui/safeMarkdown.d.ts.map +1 -0
  117. package/dist/ui/safeMarkdown.js +196 -0
  118. package/dist/ui/safeMarkdown.js.map +1 -0
  119. package/dist/ui/theme.d.ts +1 -1
  120. package/dist/ui/theme.d.ts.map +1 -1
  121. package/dist/ui/utils.d.ts +1 -1
  122. package/dist/ui/utils.d.ts.map +1 -1
  123. package/package.json +26 -2
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install @talk2view/sdk
13
13
  ### React (recommended)
14
14
 
15
15
  ```tsx
16
- import { T2VProvider, ChatPanel } from '@talk2view/sdk/react';
16
+ import { Talk2View, ChatPanel } from '@talk2view/sdk/ui';
17
17
  import type { ClientTool } from '@talk2view/sdk';
18
18
 
19
19
  const tools: ClientTool[] = [
@@ -36,17 +36,21 @@ const tools: ClientTool[] = [
36
36
 
37
37
  function App() {
38
38
  return (
39
- <T2VProvider partnerKey="pk_live_..." baseUrl="https://api.talk2view.com">
40
- <ChatPanel tools={tools} systemPrompt="You control a canvas." />
41
- </T2VProvider>
39
+ <Talk2View partnerKey="pk_live_..." tools={tools} systemPrompt="You control a canvas.">
40
+ <ChatPanel />
41
+ </Talk2View>
42
42
  );
43
43
  }
44
44
  ```
45
45
 
46
- That's it. The `ChatPanel` handles login, tool registration, streaming chat, and tool execution automatically.
46
+ That's it. `<Talk2View>` registers your tools and holds the chat state; `<ChatPanel>` is the UI — sign-in, streaming replies, tool calls and approvals. Both come from `@talk2view/sdk/ui`, and `<ChatPanel>` must be inside `<Talk2View>`.
47
+
48
+ A logged-out visitor gets an anonymous demo session automatically, so there is nothing to wire up before the first reply. Pass `allowAnonymous={false}` to `<ChatPanel>` to ask people to sign in first.
47
49
 
48
50
  ### Vanilla JavaScript
49
51
 
52
+ The package root exports a `Talk2View` **class** — the framework-agnostic client. It shares a name with the `<Talk2View>` provider component from `/ui` above; if you need both in one file, alias one of them.
53
+
50
54
  ```typescript
51
55
  import { Talk2View } from '@talk2view/sdk';
52
56
 
@@ -69,9 +73,10 @@ await t2v.tools.register([
69
73
  ]);
70
74
 
71
75
  // 3. Chat — tool calls are handled automatically
76
+ let reply = '';
72
77
  for await (const event of t2v.chat('What time is it?')) {
73
- if (event.type === 'text') process.stdout.write(event.content);
74
- if (event.type === 'done') console.log('\n');
78
+ if (event.type === 'text') reply += event.content;
79
+ if (event.type === 'done') console.log(reply);
75
80
  }
76
81
  ```
77
82
 
@@ -124,49 +129,78 @@ Tools with `return_direct: true` skip the AI's post-processing step. The tool re
124
129
 
125
130
  ## React API
126
131
 
127
- ### `<T2VProvider>`
132
+ There are three React entry points, and they do different jobs:
133
+
134
+ - **`@talk2view/sdk/ui`** — the chat UI: `<Talk2View>`, `<ChatPanel>`, `<ChatWidget>` and the components they are built from. Start here.
135
+ - **`@talk2view/sdk/react`** — headless: `<T2VProvider>` and hooks, for building your own UI.
136
+ - **`@talk2view/sdk/assistant-ui`** — a runtime for [assistant-ui](https://www.assistant-ui.com): render the stock `<Thread />` on Talk2View. See [assistant-ui](#assistant-ui) below.
128
137
 
129
- Context provider that initializes the Talk2View client. Wrap your app (or the section that uses Talk2View) with this.
138
+ ### `<Talk2View>` (from `/ui`)
139
+
140
+ The root provider for the UI components. It creates the client, registers your tools, injects the theme, and holds the chat state every `/ui` component reads.
130
141
 
131
142
  ```tsx
132
- <T2VProvider
133
- partnerKey="pk_live_..." // Required
134
- baseUrl="https://api.talk2view.com" // Optional, defaults to localhost:8100
135
- model="gpt-4.1-mini" // Optional, uses server default
143
+ <Talk2View
144
+ partnerKey="pk_live_..." // Required
145
+ tools={myTools} // Optional — ClientTool[] (with execute) or schemas
146
+ systemPrompt="You are..." // Optional — sent with every message
147
+ baseUrl="https://engine.talk2view.com" // Optional, this is the default
148
+ model="gpt-4.1-mini" // Optional, falls back to your partner default
149
+ theme={{ accent: '#26C8B8' }} // Optional
150
+ debug={false} // Optional — log to the browser console
136
151
  >
137
152
  {children}
138
- </T2VProvider>
153
+ </Talk2View>
139
154
  ```
140
155
 
141
- ### `<ChatPanel>`
156
+ ### `<ChatPanel>` (from `/ui`)
142
157
 
143
- Self-contained chat UI with built-in login, tool registration, message list, and input. Drop this in and it works.
158
+ The full chat surface: header, message list, composer, sign-in, tool approvals. It takes its client and state from `<Talk2View>`, so it must be rendered inside one — on its own it throws `useTalk2View must be used within <Talk2View>`. Note that `tools` and `systemPrompt` belong on `<Talk2View>`, not here.
144
159
 
145
160
  ```tsx
146
161
  <ChatPanel
147
- tools={myTools} // ClientTool[] — tools with execute handlers
148
- systemPrompt="You are..." // Optional system prompt
149
- signupUrl="https://..." // Optional, link shown on login form
150
- className="my-chat" // Optional CSS class
151
- style={{ height: '100%' }} // Optional inline styles
162
+ welcome={{ heading: 'Ask about this document', suggestions: ['Summarise it'] }}
163
+ allowAnonymous={true} // Default. false = require sign-in before chatting
164
+ signupUrl="https://..." // Link shown on the sign-in form
165
+ resetPasswordUrl="https://..." // Adds a "Forgot password?" link
166
+ resetPasswordTarget="_blank" // Default. '_self' opens in your own app
167
+ describeToolActivity={(name, args) => (name === 'set_color' ? 'Recolouring' : null)}
168
+ isToolDestructive={(name) => name.startsWith('delete_')}
169
+ groupAssistantMessages={false}
152
170
  />
153
171
  ```
154
172
 
155
- If the user isn't logged in, `ChatPanel` shows a login form automatically. After login, it registers the provided tools and enables the chat input.
173
+ By default a logged-out visitor is signed into an anonymous demo session rather than being shown a login form. The sign-in form appears when `allowAnonymous` is `false`, when your partner account has anonymous access switched off or its daily cap is spent, or when the demo budget for that visitor runs out.
156
174
 
157
- ### `<LoginModal>`
175
+ ### `<LoginForm>` (from `/ui`)
158
176
 
159
- Standalone login form. Use this if you want to control placement separately from the chat UI.
177
+ The sign-in form on its own, if you want to place it yourself. It renders unconditionally — you decide when to show it (check `isAuthenticated` from `useTalk2View()`, the `/ui` hook).
160
178
 
161
179
  ```tsx
162
- <LoginModal
163
- signupUrl="https://talk2view.com/auth?signup" // Optional
164
- onSuccess={() => console.log('Logged in!')} // Optional callback
165
- className="my-login" // Optional CSS class
180
+ <LoginForm
181
+ heading="Sign in"
182
+ subheading="to keep your chat history"
183
+ defaultMode="login" // or 'signup'
184
+ signupUrl="https://talk2view.com/auth?signup"
185
+ resetPasswordUrl="https://..."
186
+ termsUrl="https://..."
187
+ privacyUrl="https://..."
166
188
  />
167
189
  ```
168
190
 
169
- Returns `null` if the user is already authenticated.
191
+ ### `<T2VProvider>` (from `/react`)
192
+
193
+ The headless provider. Use it when you are building your own UI on the hooks below; `<Talk2View>` already includes it, so you never need both.
194
+
195
+ ```tsx
196
+ <T2VProvider
197
+ partnerKey="pk_live_..." // Required
198
+ baseUrl="https://engine.talk2view.com" // Optional, this is the default
199
+ model="gpt-4.1-mini" // Optional, uses your partner default
200
+ >
201
+ {children}
202
+ </T2VProvider>
203
+ ```
170
204
 
171
205
  ### `useT2V()`
172
206
 
@@ -187,30 +221,45 @@ const {
187
221
  isLoading, // boolean
188
222
  error, // string | null
189
223
  login, // (email: string, password: string) => Promise<void>
190
- signup, // (email: string, password: string) => Promise<void>
224
+ signup, // (email, password) => Promise<{ user, confirmationRequired }>
191
225
  logout, // () => Promise<void>
192
226
  clearError, // () => void
227
+ signInWithGoogle, // () => Promise<void> — opens the Google sign-in popup
228
+ signInWithApple, // () => Promise<void> — opens the Apple sign-in popup
229
+ oauthLoading, // boolean — true while either popup is open
230
+ oauthProvider, // 'google' | 'apple' | null — which one
231
+ oauthProviders, // ('google' | 'apple')[] | null — popup sign-ins available on this website; null while loading
193
232
  } = useT2VAuth();
194
233
  ```
195
234
 
235
+ `signup()` resolves to a `SignupOutcome`: `{ user: User | null; confirmationRequired: boolean }`. When your project has email confirmation switched on, `confirmationRequired` is `true` and `user` is `null` until the person clicks the link in their inbox.
236
+
237
+ Google and Apple sign-in open a popup and work on any `https://` website, with nothing to configure — your users have Talk2View accounts, and the sign-in form is the same in every app. Your publishable key is public, though, so on a website you have **not** registered your users first see a Talk2View screen naming the site that is asking, and press Continue. Add your site under **Settings → Allowed websites** in the dashboard (the exact `https://` address, nothing after the domain) and that screen goes away; the SDK logs one line to the console when it applies. If your account is set to allow only registered websites, `<LoginForm>` hides the two buttons elsewhere. Popup sign-in never runs on `localhost`; use email and password while developing.
238
+
196
239
  ### `useT2VChat()`
197
240
 
198
241
  Chat state and message sending.
199
242
 
200
243
  ```tsx
201
244
  const {
202
- messages, // DisplayMessage[]
203
- isLoading, // boolean — true while streaming
204
- error, // string | null
205
- threadId, // string | null
206
- agentStatus, // { type: string; message: string } | null — real-time agent status
207
- todos, // string — agent's current todo/plan text
208
- sendMessage, // (content: string) => Promise<void>
209
- clearMessages, // () => void
210
- clearError, // () => void
245
+ messages, // DisplayMessage[]
246
+ isLoading, // boolean — true while streaming
247
+ error, // string | null
248
+ threadId, // string | null
249
+ agentStatus, // { status: string; message: string } | null
250
+ pendingApproval, // PendingApproval | null — a tool call awaiting a decision
251
+ alwaysAllowedTools, // ReadonlySet<string> — approved for the rest of the session
252
+ sendMessage, // (content, { attachments? }) => Promise<void>
253
+ approveToolCall, // (decision: HumanDecision) => Promise<void>
254
+ retryLastMessage, // () => Promise<void> — only meaningful while error is set
255
+ stop, // () => void — ends the reply, keeps the text so far
256
+ clearMessages, // () => void
257
+ clearError, // () => void
211
258
  } = useT2VChat({ systemPrompt: 'You are a helpful assistant.' });
212
259
  ```
213
260
 
261
+ When a tool is registered with `permission: true`, the agent pauses and `pendingApproval` fills in. Show it, then call `approveToolCall({ action: 'once' | 'always' | 'deny' })` — `'always'` adds the tool to `alwaysAllowedTools` for the rest of the session. `<ChatPanel>` already does all of this; you only need it when building your own UI.
262
+
214
263
  `DisplayMessage` shape:
215
264
  ```typescript
216
265
  {
@@ -218,7 +267,10 @@ const {
218
267
  role: 'user' | 'assistant'
219
268
  content: string
220
269
  timestamp: Date
221
- isStreaming?: boolean // true while the assistant is still generating
270
+ isStreaming?: boolean // true while the assistant is still generating
271
+ attachments?: Attachment[] // files sent with a user message
272
+ plan?: string // markdown checklist from the agent's planning tool
273
+ steps?: ToolStep[] // completed tool calls, rendered as inline steps
222
274
  }
223
275
  ```
224
276
 
@@ -236,6 +288,86 @@ const {
236
288
 
237
289
  ---
238
290
 
291
+ ## assistant-ui
292
+
293
+ assistant-ui already has a seam other backends plug into at the runtime level — `@assistant-ui/react-ai-sdk` for the Vercel AI SDK is the well-known example. `useTalk2ViewRuntime` is Talk2View's equivalent: it hands assistant-ui an `AssistantRuntime`, built on `useExternalStoreRuntime`, so the stock `<Thread />` (or the primitives) render Talk2View's messages, client tools, approvals and streaming — with no Talk2View-specific component in the tree.
294
+
295
+ This is a separate entry point from `/ui` and `/react`: `@assistant-ui/react` is an optional peer dependency, loaded only when you import `@talk2view/sdk/assistant-ui`. It never affects the root, `/react` or `/ui` bundles.
296
+
297
+ ### Install
298
+
299
+ ```bash
300
+ npm install @assistant-ui/react
301
+ npx shadcn@latest init --yes --defaults
302
+ npx shadcn@latest add @assistant-ui/thread --yes
303
+ ```
304
+
305
+ The `shadcn` commands generate the Thread's source into your own `src/components/` — they're your files, not a Talk2View package, so you can restyle or extend them freely. See `examples/react-assistant-ui` for a full app built this way, including the exact generated output.
306
+
307
+ One known wrinkle in the registry output at the time of writing: `tooltip-icon-button.tsx` passes `delayDuration={0}` to a `TooltipProvider` whose prop is `delay`. It is inert at runtime, but it fails `tsc`, so if your build type-checks (most Vite templates run `tsc -b && vite build`) change it to `delay={0}` — the example does.
308
+
309
+ ### `useTalk2ViewRuntime`
310
+
311
+ ```tsx
312
+ import { AssistantRuntimeProvider } from '@assistant-ui/react';
313
+ import { useTalk2ViewRuntime } from '@talk2view/sdk/assistant-ui';
314
+
315
+ function Chat() {
316
+ const runtime = useTalk2ViewRuntime({ partnerKey: 'pk_live_...', tools });
317
+ return (
318
+ <AssistantRuntimeProvider runtime={runtime}>
319
+ <Thread />
320
+ </AssistantRuntimeProvider>
321
+ );
322
+ }
323
+ ```
324
+
325
+ `useTalk2ViewRuntime(options)` takes the same connection options as `<T2VProvider>` (`partnerKey`, `baseUrl`, `model`, `debug`, `anonymousAutoStart`) plus `tools` and `systemPrompt`, and creates its own `Talk2View` client — a new one only when `partnerKey`, `baseUrl`, `debug` or `anonymousAutoStart` changes (`requestTimeout` is read once, at creation). `model` is sent with each message rather than baked into the client, so an end-user can switch models from a settings screen without losing the chat. `tools` are registered with the engine when their schemas change, compared by value, so an inline array is fine.
326
+
327
+ ### `useTalk2ViewRuntimeForClient`
328
+
329
+ For an app that already has a `Talk2View` client — for example from `<T2VProvider>`, so auth and chat state stay shared with the rest of the app — pass it in instead of creating a second one:
330
+
331
+ ```tsx
332
+ import { AssistantRuntimeProvider } from '@assistant-ui/react';
333
+ import { useT2V } from '@talk2view/sdk/react';
334
+ import { useTalk2ViewRuntimeForClient } from '@talk2view/sdk/assistant-ui';
335
+
336
+ function Chat() {
337
+ const { t2v } = useT2V(); // from <T2VProvider>
338
+ const runtime = useTalk2ViewRuntimeForClient(t2v, { tools });
339
+ return (
340
+ <AssistantRuntimeProvider runtime={runtime}>
341
+ <Thread />
342
+ </AssistantRuntimeProvider>
343
+ );
344
+ }
345
+ ```
346
+
347
+ ### What maps to what
348
+
349
+ | assistant-ui | Talk2View SDK |
350
+ | --- | --- |
351
+ | Streaming replies | `DisplayMessage`s from `t2v.messages`, converted per-message by `toThreadMessage()` |
352
+ | Client tools | `tools` registers schemas via `t2v.tools.register()` and handlers via `t2v.tools.handle()` — same as `<Talk2View tools>` |
353
+ | Tool approvals | A `PendingApproval` becomes assistant-ui's own approval card (`allow-once` / `allow-always` / `reject-once`, plus a free-form reason); the decision is sent through `t2v.approveToolCall()` |
354
+ | Attachments | assistant-ui's `AttachmentAdapter` uploads through `t2v.uploadAttachment()` |
355
+ | Stop | assistant-ui's cancel action calls `t2v.stop()` — or, while a tool call is waiting for a decision, denies it with the reason "Cancelled by the user" |
356
+ | Retry | assistant-ui's reload action on the **latest turn** — its reply, or the error card when it failed — calls `t2v.retryLastMessage()`; on an older reply it does nothing, because the SDK can only regenerate the last turn |
357
+
358
+ Four things do not map, on purpose:
359
+
360
+ - **Editing tool arguments before approval.** The SDK supports it (`HumanDecision.updatedInput`, used by `<ApprovalCard>` in `/ui`), but the stock assistant-ui approval card has no edit-and-resubmit affordance to drive it from — it can only approve, deny, or attach a free-form reason.
361
+ - **Sign-in UI.** `useTalk2ViewRuntime` only creates a client and a runtime; there is no `<LoginForm>` equivalent. A logged-out visitor gets an anonymous demo session automatically (same default as `<ChatPanel>`), and building your own sign-in UI, or gating the app before it mounts, is on the partner.
362
+ - **The agent's plan.** The planning checklist `<ChatPanel>` shows arrives as a `data` part named `plan` (`{ markdown }`). The stock Thread has no renderer for it, so it is silently not shown; register one to see it: `<MessagePrimitive.Parts components={{ data: { by_name: { plan: PlanView } } }} />`.
363
+ - **Editing and deleting sent messages.** The runtime supplies no `onEdit`/`onDelete`, so the stock Thread's pencil on user messages stays disabled. Remove it from your copy of the Thread if you'd rather not show it.
364
+
365
+ ### Bundle size
366
+
367
+ `<Thread />` and its dependencies are the partner's own assistant-ui bundle, sized however assistant-ui and its registry components size it; `@talk2view/sdk/assistant-ui` itself adds about 1.9 KB gzipped on top (`dist/assistant-ui/{index,convert}.js`, bundled and minified with esbuild, `react`, `react-dom`, `@assistant-ui/react` and the SDK's own `Talk2View` client marked external since a partner already has them).
368
+
369
+ ---
370
+
239
371
  ## Core API (framework-agnostic)
240
372
 
241
373
  ### `Talk2View`
@@ -245,7 +377,7 @@ Main entry point. Use this directly for vanilla JS or non-React frameworks.
245
377
  ```typescript
246
378
  const t2v = new Talk2View({
247
379
  partnerKey: 'pk_live_...', // Required
248
- baseUrl: 'https://api.talk2view.com', // Optional
380
+ baseUrl: 'https://engine.talk2view.com', // Optional, this is the default
249
381
  model: 'gpt-4.1-mini', // Optional
250
382
  });
251
383
  ```
@@ -254,7 +386,7 @@ const t2v = new Talk2View({
254
386
 
255
387
  ```typescript
256
388
  await t2v.auth.login(email, password) // Returns User
257
- await t2v.auth.signup(email, password) // Returns User
389
+ await t2v.auth.signup(email, password) // Returns { user, confirmationRequired }
258
390
  await t2v.auth.logout()
259
391
  t2v.auth.getUser() // Returns User | null
260
392
  t2v.auth.isAuthenticated() // Returns boolean
@@ -358,6 +490,24 @@ for await (const event of t2v.chat('Zoom in please')) {
358
490
  }
359
491
  ```
360
492
 
493
+ #### `t2v.warmUp()`
494
+
495
+ A new end-user's first AI call is slower than the rest: the engine sets up their
496
+ account with the model proxy on first use, which takes a few seconds. Call
497
+ `warmUp()` at the first sign they are going to use the chat — their first
498
+ keystroke, a tap on the mic — and that setup happens while they are still typing.
499
+
500
+ ```ts
501
+ input.addEventListener('input', () => t2v.warmUp(), { once: true });
502
+ ```
503
+
504
+ It is safe to call repeatedly (one request per signed-in end-user), never
505
+ throws, and is a no-op against an engine that predates it. Don't call it when
506
+ the chat merely opens: for a logged-out visitor it starts an anonymous session,
507
+ which counts toward your daily anonymous cap.
508
+
509
+ `<ChatWidget>`, `<ChatPanel>` and the assistant-ui runtime already do this.
510
+
361
511
  #### `t2v.clearSession()`
362
512
 
363
513
  Reset the current session. Clears the thread ID so the next `chat()` call starts a fresh conversation.
@@ -379,8 +529,15 @@ const audioModels = await t2v.listAudioModels(); // Speech-to-text models
379
529
 
380
530
  Transcribe an audio file using the server's speech-to-text service:
381
531
 
532
+ Takes a `FormData`, with the fields the engine's `/v1/audio/transcriptions` endpoint accepts (`file`, `model`, and optionally `language`, `translate_to`, `translation_guidance`):
533
+
382
534
  ```typescript
383
- const result = await t2v.transcribe(audioBlob, 'whisper-1', 'en');
535
+ const form = new FormData();
536
+ form.append('file', audioBlob, 'recording.webm');
537
+ form.append('model', 'faster-whisper-large-turbo-gcp'); // one of listAudioModels()
538
+ form.append('language', 'en'); // optional
539
+
540
+ const result = await t2v.transcribe(form);
384
541
  console.log(result.text);
385
542
  ```
386
543
 
@@ -492,7 +649,7 @@ Use `enum` to restrict string values:
492
649
  The `execute` function receives the AI's arguments as a `Record<string, unknown>` and must return a `Promise<string>`. The returned string is sent back to the AI as the tool's result.
493
650
 
494
651
  - Return `JSON.stringify(...)` for structured results
495
- - Throw an error or return an error string if the tool fails — the SDK catches errors and sends them back to the AI as `{ result: "Error: ...", is_error: true }`
652
+ - Throw an error or return an error string if the tool fails — the SDK catches it and sends the message back to the AI as `{ result: '{"error":"..."}', is_error: true }`, so the agent can explain or try something else
496
653
 
497
654
  ---
498
655
 
@@ -523,22 +680,62 @@ To mitigate this:
523
680
 
524
681
  ---
525
682
 
683
+ ## Rendering model replies
684
+
685
+ Treat every model reply as untrusted. Prompt injection through web search, documents, email or tool results can make the model write anything. `<ChatPanel>` renders replies as markdown only, and never loads remote content on its own:
686
+
687
+ - **Raw HTML** in a reply is shown as text, apart from a bare `<br>`, which is kept so a GFM table cell can break a line (a cell can't hold a literal newline).
688
+ - **Images** become links (`Image: <alt> (<host>)`) that the end-user can choose to open.
689
+ - **Links** open in a new tab with `noopener noreferrer`, and only `http(s)` and `mailto` link destinations are kept.
690
+
691
+ If you build your own chat UI, render replies the same way:
692
+
693
+ ```tsx
694
+ import { renderSafeMarkdown } from '@talk2view/sdk/ui';
695
+
696
+ <div dangerouslySetInnerHTML={{ __html: renderSafeMarkdown(reply) }} />
697
+ ```
698
+
699
+ Never pass model output to `innerHTML` or a markdown library's raw HTML output without it.
700
+
701
+ Call it in the browser (client components): without a DOM it returns the reply as escaped plain text.
702
+
703
+ ## Chat sessions and deploys
704
+
705
+ A chat session lives in the engine's memory, so a Talk2View deploy, a restart, or eviction under load can end it. The SDK handles most of that for you:
706
+
707
+ - **Sending a message:** if the session is gone before the reply has started, the SDK opens a new one, re-registers your tools and resends the turn with the conversation so far — nothing from that turn is lost. It emits `sessionRecovered` with the new session id, which you can log.
708
+ - **Answering an approval:** the SDK can't safely resend the tool result, because your handler has already run. It emits one `error` event with `errorType: 'session_lost'` and clears the pending approval, so the end-user can send their message again.
709
+ - **Mid-reply or mid-tool-resume:** if the chat session is lost after the reply has started, or while a tool result is being sent back, the SDK reports `session_lost` and doesn't resend, because the tool handler may already have run.
710
+
711
+ ```ts
712
+ t2v.on('sessionRecovered', (sessionId) => console.info('reconnected', sessionId));
713
+ ```
714
+
715
+ This is why `sendMessage()` and `chat()` take the conversation history: the engine works out which messages are new, so a fresh session continues where the old one stopped.
716
+
526
717
  ## Error Handling
527
718
 
528
719
  The SDK throws typed errors:
529
720
 
530
- | Error Class | When |
531
- |-------------|------|
532
- | `AuthenticationError` | Invalid credentials, expired token |
533
- | `PartnerKeyError` | Invalid or inactive partner API key |
534
- | `SessionError` | Session not found or creation failed |
535
- | `NetworkError` | Network failure, server unreachable |
536
- | `T2VError` | Base class for all SDK errors |
721
+ | Error Class | When | `err.type` |
722
+ |-------------|------|------------|
723
+ | `AuthenticationError` | Bad credentials, expired or rejected token | `authentication_error` |
724
+ | `PartnerKeyError` | Unknown or inactive partner key | `partner_key_error` |
725
+ | `NetworkError` | Network failure, server unreachable, request timed out | `network_error` |
726
+ | `T2VError` | Base class — and what everything else arrives as | the engine's own type |
727
+ | `SessionError` | Reserved — see below | `session_not_found` |
728
+
729
+ Every error carries the engine's machine-readable `type`, so `err.type` is the precise test; the classes above are a convenience for the common cases. Types worth knowing that arrive as a plain `T2VError`: `origin_not_allowed` (403 — the request's Origin isn't on your key's allow-list), `not_found` (404), and `rate_limit_exceeded` (429).
730
+
731
+ Some failures never reach a `catch` at all, because they arrive inside the chat stream rather than as a rejected request: a spent anonymous demo budget comes through as an `error` event with `errorType: 'budget_exceeded'`, which the SDK turns into a `demoLimitReached` event rather than an error state. Listen for that instead of trying to catch it.
537
732
 
538
- All errors extend `T2VError`, which exposes `message`, `type`, `statusCode`, and `code`:
733
+ **On lost chat sessions:** `SessionError` is reserved for a dedicated engine type that isn't in use yet — today a chat session that no longer exists comes back as a `T2VError` with `type: 'not_found'` and `statusCode: 404`. You shouldn't need to catch it: the SDK recovers the session itself where that is safe, and otherwise reports an `error` event with `errorType: 'session_lost'`. See [Chat sessions and deploys](#chat-sessions-and-deploys).
734
+
735
+ All errors extend `T2VError`, which exposes `message`, `type`, `statusCode`, `code`, and `detail` (the server's raw diagnostic text, kept out of `message` so it never reaches an end-user by accident):
539
736
 
540
737
  ```typescript
541
- import { T2VError, AuthenticationError, NetworkError, SessionError } from '@talk2view/sdk';
738
+ import { T2VError, AuthenticationError, NetworkError, PartnerKeyError } from '@talk2view/sdk';
542
739
 
543
740
  try {
544
741
  await t2v.auth.login(email, password);
@@ -547,8 +744,8 @@ try {
547
744
  console.log('Bad credentials');
548
745
  } else if (err instanceof NetworkError) {
549
746
  console.log('Server unreachable');
550
- } else if (err instanceof SessionError) {
551
- console.log('Session issue:', err.message);
747
+ } else if (err instanceof PartnerKeyError) {
748
+ console.log('Check your partner key:', err.message);
552
749
  } else if (err instanceof T2VError) {
553
750
  // Access the error code for programmatic handling
554
751
  console.log(`Error [${err.code}]: ${err.message} (HTTP ${err.statusCode})`);
@@ -575,18 +772,25 @@ const { error: chatError } = useT2VChat({ systemPrompt: '...' });
575
772
  | Property | Type | Required | Default | Description |
576
773
  |----------|------|----------|---------|-------------|
577
774
  | `partnerKey` | `string` | Yes | — | Your partner API key |
578
- | `baseUrl` | `string` | No | `'http://localhost:8100'` | Talk2View API server URL |
775
+ | `baseUrl` | `string` | No | `'https://engine.talk2view.com'` | Talk2View engine URL |
579
776
  | `model` | `string` | No | Server default | LLM model to use |
580
777
  | `requestTimeout` | `number` | No | `30000` | HTTP request timeout in ms. Does not apply to SSE streams after connection. |
778
+ | `anonymousAutoStart` | `boolean` | No | `true` | Start an anonymous demo session on the first `chat()` when nobody is signed in. Set `false` to require sign-in. |
779
+ | `debug` | `boolean` | No | `false` | Log streaming events, tool calls, history and state changes to the console. |
581
780
 
582
781
  ---
583
782
 
584
783
  ## System Prompts
585
784
 
586
- System prompts set the AI's behavior and context for your application. Pass them to `ChatPanel` or `useT2VChat`:
785
+ System prompts set the AI's behavior and context for your application. Pass one to `<Talk2View>` (the UI components) or to `useT2VChat` (the hooks):
587
786
 
588
787
  ```tsx
589
- <ChatPanel systemPrompt="You are a medical imaging assistant. Use the provided tools to control the DICOM viewer." />
788
+ <Talk2View
789
+ partnerKey="pk_live_..."
790
+ systemPrompt="You are a medical imaging assistant. Use the provided tools to control the DICOM viewer."
791
+ >
792
+ <ChatPanel />
793
+ </Talk2View>
590
794
  ```
591
795
 
592
796
  Best practices:
@@ -603,7 +807,7 @@ Here's a complete example integrating Talk2View into a hypothetical drawing app:
603
807
 
604
808
  ```tsx
605
809
  import React, { useRef, useMemo } from 'react';
606
- import { T2VProvider, ChatPanel } from '@talk2view/sdk/react';
810
+ import { Talk2View, ChatPanel } from '@talk2view/sdk/ui';
607
811
  import type { ClientTool } from '@talk2view/sdk';
608
812
  import { Canvas } from './Canvas';
609
813
 
@@ -656,16 +860,18 @@ function App() {
656
860
  ], []);
657
861
 
658
862
  return (
659
- <T2VProvider partnerKey="pk_live_..." baseUrl="https://api.talk2view.com">
863
+ <Talk2View
864
+ partnerKey="pk_live_..."
865
+ tools={tools}
866
+ systemPrompt="You control a drawing canvas. Use the tools to draw shapes and manage the canvas."
867
+ >
660
868
  <div style={{ display: 'flex', height: '100vh' }}>
661
869
  <Canvas ref={canvasRef} style={{ flex: 1 }} />
662
- <ChatPanel
663
- tools={tools}
664
- systemPrompt="You control a drawing canvas. Use the tools to draw shapes and manage the canvas."
665
- style={{ width: 360 }}
666
- />
870
+ <div style={{ width: 360 }}>
871
+ <ChatPanel welcome={{ heading: 'What should I draw?' }} />
872
+ </div>
667
873
  </div>
668
- </T2VProvider>
874
+ </Talk2View>
669
875
  );
670
876
  }
671
877
  ```
@@ -694,16 +900,32 @@ import type {
694
900
  RegisterSkillsResponse,
695
901
  } from '@talk2view/sdk';
696
902
 
697
- // React types
903
+ // Headless React types
698
904
  import type {
699
905
  T2VProviderProps,
700
906
  UseT2VAuthResult,
701
907
  UseT2VChatResult,
702
908
  UseT2VToolsResult,
909
+ UsePartnerConfigResult,
910
+ UseUserPreferencesResult,
703
911
  DisplayMessage,
704
- ChatPanelProps,
705
- LoginModalProps,
912
+ ToolStep,
913
+ PendingApproval,
706
914
  } from '@talk2view/sdk/react';
915
+
916
+ // UI component types
917
+ import type {
918
+ Talk2ViewProps,
919
+ ChatPanelProps,
920
+ ChatWidgetProps,
921
+ } from '@talk2view/sdk/ui';
922
+
923
+ // assistant-ui runtime
924
+ import { useTalk2ViewRuntime, useTalk2ViewRuntimeForClient } from '@talk2view/sdk/assistant-ui';
925
+ import type {
926
+ UseTalk2ViewRuntimeOptions, // useTalk2ViewRuntime(options)
927
+ Talk2ViewRuntimeOptions, // useTalk2ViewRuntimeForClient(t2v, options)
928
+ } from '@talk2view/sdk/assistant-ui';
707
929
  ```
708
930
 
709
931
  ## Publishing
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pure mapping from the SDK's chat state onto assistant-ui's message shape.
3
+ *
4
+ * assistant-ui renders `ThreadMessageLike`; the SDK keeps `DisplayMessage`.
5
+ * Everything here is a function of (message, surrounding state) so it can be
6
+ * unit-tested without React or a runtime.
7
+ */
8
+ import type { ThreadMessageLike } from '@assistant-ui/react';
9
+ import type { DisplayMessage, HumanDecision, PendingApproval } from '../types.js';
10
+ /** What a message needs to know about the thread around it. */
11
+ export interface ConvertContext {
12
+ /** True for the newest message — the only one that can be streaming or waiting on a decision. */
13
+ isLast: boolean;
14
+ /** A tool call waiting for the end-user; rendered as an approval gate on the last assistant message. */
15
+ pendingApproval: PendingApproval | null;
16
+ /** The SDK's error state; shown on the last assistant message. */
17
+ error: string | null;
18
+ }
19
+ type ThreadPart = Extract<ThreadMessageLike['content'], readonly unknown[]>[number];
20
+ /** Option ids the stock assistant-ui approval card sends back; we map them onto `HumanDecision`. */
21
+ export declare const APPROVAL_OPTION_IDS: {
22
+ readonly once: "allow-once";
23
+ readonly always: "allow-always";
24
+ readonly deny: "reject-once";
25
+ };
26
+ /** Stable id for a completed step: steps carry no id of their own. */
27
+ export declare function stepToolCallId(messageId: string, index: number): string;
28
+ /** The approval gate assistant-ui draws for a tool call that needs a decision. */
29
+ export declare function approvalPart(approval: PendingApproval): ThreadPart;
30
+ /** Convert one SDK message for assistant-ui. */
31
+ export declare function toThreadMessage(msg: DisplayMessage, ctx: ConvertContext): ThreadMessageLike;
32
+ /** Turn the stock approval card's answer into the SDK's `HumanDecision`. */
33
+ export declare function toHumanDecision(response: {
34
+ approved: boolean;
35
+ optionId?: string;
36
+ text?: string;
37
+ reason?: string;
38
+ }): HumanDecision;
39
+ export {};
40
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/assistant-ui/convert.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAuB,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAc,cAAc,EAAE,aAAa,EAAE,eAAe,EAAY,MAAM,aAAa,CAAC;AAExG,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,iGAAiG;IACjG,MAAM,EAAE,OAAO,CAAC;IAChB,wGAAwG;IACxG,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAGD,KAAK,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpF,oGAAoG;AACpG,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAQX,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE;AAgBD,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,UAAU,CAelE;AAkCD,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,GAAG,iBAAiB,CA0B3F;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,QAAQ,EAAE;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,aAAa,CAShB"}