@dudousxd/nestjs-agent-react 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +296 -8
- package/dist/index.cjs +2189 -758
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +980 -224
- package/dist/index.d.ts +980 -224
- package/dist/index.js +2175 -758
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ai from 'ai';
|
|
2
2
|
import { ChatTransport, UIMessage, UIMessageChunk, ToolUIPart, DynamicToolUIPart } from 'ai';
|
|
3
3
|
import * as _dudousxd_nestjs_agent_core from '@dudousxd/nestjs-agent-core';
|
|
4
|
-
import { ThreadSummary, ThreadDetail, MessageAttachment, QuotaView
|
|
4
|
+
import { StoredMessage, ThreadSummary, SkillCatalogEntry, ThreadDetail, MessageAttachment, QuotaView } from '@dudousxd/nestjs-agent-core';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
@@ -75,11 +75,26 @@ declare class AgentChatTransport implements ChatTransport<UIMessage> {
|
|
|
75
75
|
private readonly options;
|
|
76
76
|
private currentRunId;
|
|
77
77
|
private currentThreadId;
|
|
78
|
+
/**
|
|
79
|
+
* One attempt at a time. The AI SDK keeps a single in-flight response per chat and decides
|
|
80
|
+
* whether its streamed message REPLACES the list's last entry or is appended by comparing that
|
|
81
|
+
* response's message id against the last entry alone. Two attempts writing into one chat
|
|
82
|
+
* therefore append alternating copies of each other's message, and the list ends up holding
|
|
83
|
+
* several entries under each id — which React renders as duplicate keys.
|
|
84
|
+
*/
|
|
85
|
+
private attemptLive;
|
|
78
86
|
constructor(options?: AgentChatTransportOptions);
|
|
79
87
|
/** Run id of the most recent stream — HITL approve/reject target this. */
|
|
80
88
|
get runId(): string | undefined;
|
|
81
89
|
/** Thread id of the most recent stream. */
|
|
82
90
|
get threadId(): string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* True from the moment a send or resume attempt starts until its chunk stream terminates. A
|
|
93
|
+
* caller that can refuse a turn BEFORE the SDK commits to one — `useAgentChat` wrapping
|
|
94
|
+
* `sendMessage` — reads this; the transport itself can only refuse a resume, which is the one
|
|
95
|
+
* attempt the SDK lets a transport decline.
|
|
96
|
+
*/
|
|
97
|
+
get isAttemptLive(): boolean;
|
|
83
98
|
sendMessages(options: Parameters<ChatTransport<UIMessage>['sendMessages']>[0]): Promise<ReadableStream<UIMessageChunk>>;
|
|
84
99
|
reconnectToStream(options: Parameters<ChatTransport<UIMessage>['reconnectToStream']>[0]): Promise<ReadableStream<UIMessageChunk> | null>;
|
|
85
100
|
private fetchImpl;
|
|
@@ -102,6 +117,37 @@ declare class AgentChatTransport implements ChatTransport<UIMessage> {
|
|
|
102
117
|
private toChunkStream;
|
|
103
118
|
}
|
|
104
119
|
|
|
120
|
+
/**
|
|
121
|
+
* A sub-agent the conversation started and did not wait for. The chat is free while it works, so a
|
|
122
|
+
* client has to be able to say what is still running and to show the answer when it lands — neither
|
|
123
|
+
* of which the turn that started it can report, because that turn is over.
|
|
124
|
+
*/
|
|
125
|
+
interface BackgroundRun {
|
|
126
|
+
/** The run doing the work. Also what stamps the message it eventually posts. */
|
|
127
|
+
runId: string;
|
|
128
|
+
/** The agent working on it. */
|
|
129
|
+
agent: string;
|
|
130
|
+
/** The `agent`-kind call that started it — what a UI keys its card off. */
|
|
131
|
+
toolCallId: string;
|
|
132
|
+
toolName: string;
|
|
133
|
+
/**
|
|
134
|
+
* `delivered` the moment the thread holds a message stamped with this run. A run that FAILED
|
|
135
|
+
* reads as delivered too: it posts a message saying so, which is the point — there is no third
|
|
136
|
+
* state a reader could act on differently, and "running" for ever is the state to avoid.
|
|
137
|
+
*/
|
|
138
|
+
status: 'running' | 'delivered';
|
|
139
|
+
/** The message it posted, once it did. */
|
|
140
|
+
message?: StoredMessage;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Every sub-agent this thread started in the background, and whether each has reported back.
|
|
144
|
+
*
|
|
145
|
+
* Derived from the transcript rather than tracked in session state, so it survives a reload and a
|
|
146
|
+
* second tab: the receipt is a persisted tool result, and the answer is a persisted message stamped
|
|
147
|
+
* with the run that wrote it. Nothing here needs a live connection to be true.
|
|
148
|
+
*/
|
|
149
|
+
declare function backgroundRunsFromThread(messages: readonly StoredMessage[]): BackgroundRun[];
|
|
150
|
+
|
|
105
151
|
/**
|
|
106
152
|
* Thrown by {@link AgentClient} on a non-2xx response. Carries the HTTP `status` so callers can
|
|
107
153
|
* branch (e.g. 403 → "not your thread", 429 → quota) instead of string-matching a generic Error.
|
|
@@ -149,6 +195,13 @@ declare class AgentClient {
|
|
|
149
195
|
private readonly options;
|
|
150
196
|
constructor(options?: AgentClientOptions);
|
|
151
197
|
listThreads(): Promise<ThreadSummary[]>;
|
|
198
|
+
/**
|
|
199
|
+
* The skills this caller can invoke right now, scope-resolved — the same list, built by the same
|
|
200
|
+
* call, that the model is offered, so what a user can type after a `/` and what the agent can
|
|
201
|
+
* reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
|
|
202
|
+
* one conversation; omitted, the server reads it as a brand-new thread.
|
|
203
|
+
*/
|
|
204
|
+
listSkills(threadId?: string): Promise<SkillCatalogEntry[]>;
|
|
152
205
|
getThread(id: string): Promise<ThreadDetail>;
|
|
153
206
|
deleteThread(id: string): Promise<void>;
|
|
154
207
|
forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
|
|
@@ -156,25 +209,876 @@ declare class AgentClient {
|
|
|
156
209
|
/** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
157
210
|
updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
|
|
158
211
|
/**
|
|
159
|
-
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
160
|
-
* mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
|
|
161
|
-
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
212
|
+
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
213
|
+
* mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
|
|
214
|
+
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
215
|
+
*/
|
|
216
|
+
uploadAttachment(file: File): Promise<MessageAttachment>;
|
|
217
|
+
promoteThread(id: string): Promise<OkResult>;
|
|
218
|
+
truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
|
|
219
|
+
getQuotaToday(): Promise<QuotaToday>;
|
|
220
|
+
cancelStream(runId: string): Promise<CancelResult>;
|
|
221
|
+
approveToolCall(input: {
|
|
222
|
+
toolCallId: string;
|
|
223
|
+
}): Promise<void>;
|
|
224
|
+
rejectToolCall(input: {
|
|
225
|
+
toolCallId: string;
|
|
226
|
+
reason?: string;
|
|
227
|
+
}): Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Settle a parked question set. `answers` is questionId → chosen option values; a question left
|
|
230
|
+
* out takes the pre-picked default the request carried, resolved server-side against the request
|
|
231
|
+
* the run already holds. Omit the whole object and the user has confirmed every pre-picked
|
|
232
|
+
* answer — which is the point of the surface, so it is a valid submission rather than a blank.
|
|
233
|
+
*/
|
|
234
|
+
answerToolCall(input: {
|
|
235
|
+
toolCallId: string;
|
|
236
|
+
answers?: Record<string, string[]>;
|
|
237
|
+
}): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
|
|
240
|
+
* values a confirmation would, and persists differently on purpose — only one of them is
|
|
241
|
+
* evidence the user chose them.
|
|
242
|
+
*/
|
|
243
|
+
skipToolCall(input: {
|
|
244
|
+
toolCallId: string;
|
|
245
|
+
}): Promise<void>;
|
|
246
|
+
private request;
|
|
247
|
+
private handleResponse;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface ChatInputProps {
|
|
251
|
+
onSubmit: (text: string) => void;
|
|
252
|
+
disabled?: boolean;
|
|
253
|
+
placeholder?: string;
|
|
254
|
+
rows?: number;
|
|
255
|
+
/** Enable the dictation mic button (Web Speech API). Default `true`. */
|
|
256
|
+
enableSpeech?: boolean;
|
|
257
|
+
/** BCP-47 locale for dictation. Default `en-US`. */
|
|
258
|
+
speechLang?: string;
|
|
259
|
+
/** Label/content for the send button. Default `"Send"`. */
|
|
260
|
+
sendLabel?: React.ReactNode;
|
|
261
|
+
/**
|
|
262
|
+
* Cancel the turn in flight — wire to `useAgentChat`'s `cancel`. Rendered only alongside
|
|
263
|
+
* `isStreaming`, so the affordance exists exactly while there is something to cancel.
|
|
264
|
+
*/
|
|
265
|
+
onStop?: () => void | Promise<void>;
|
|
266
|
+
/** Whether a turn is currently in flight. Gates the stop affordance. */
|
|
267
|
+
isStreaming?: boolean;
|
|
268
|
+
/** Label/content for the stop button. Default `"Stop generating"` — the mic's own is `"Stop"`. */
|
|
269
|
+
stopLabel?: React.ReactNode;
|
|
270
|
+
/** Wrapper class. The component ships no styles of its own. */
|
|
271
|
+
className?: string;
|
|
272
|
+
textareaClassName?: string;
|
|
273
|
+
sendButtonClassName?: string;
|
|
274
|
+
micButtonClassName?: string;
|
|
275
|
+
stopButtonClassName?: string;
|
|
276
|
+
/**
|
|
277
|
+
* Custom mic affordance. Receives `isListening`; return any node. When
|
|
278
|
+
* omitted a plain text button ("Speak" / "Stop") is rendered.
|
|
279
|
+
*/
|
|
280
|
+
renderMic?: (state: {
|
|
281
|
+
isListening: boolean;
|
|
282
|
+
}) => React.ReactNode;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Styling-agnostic chat composer. Enter submits, Shift+Enter inserts a
|
|
286
|
+
* newline. Optional dictation streams interim transcripts into the draft
|
|
287
|
+
* and commits finalized chunks, and `onStop` surfaces the backend's
|
|
288
|
+
* cancel next to send. No design-system or icon-library deps —
|
|
289
|
+
* style it entirely through the `*ClassName` props.
|
|
290
|
+
*/
|
|
291
|
+
declare function ChatInput({ onSubmit, disabled, placeholder, rows, enableSpeech, speechLang, sendLabel, onStop, isStreaming, stopLabel, className, textareaClassName, sendButtonClassName, micButtonClassName, stopButtonClassName, renderMic, }: ChatInputProps): react_jsx_runtime.JSX.Element;
|
|
292
|
+
|
|
293
|
+
/** A tool UI part on a `UIMessage` — a static `tool-*` part or the `dynamic-tool` part. */
|
|
294
|
+
type AnyToolUIPart = ToolUIPart | DynamicToolUIPart;
|
|
295
|
+
type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
|
|
296
|
+
/** Server-aggregated usage for an assistant turn. */
|
|
297
|
+
interface MessageUsageInfo {
|
|
298
|
+
inputTokens: number;
|
|
299
|
+
outputTokens: number;
|
|
300
|
+
/**
|
|
301
|
+
* `null` when no price is on record for the model that ran the turn. Distinct from `0`, which is
|
|
302
|
+
* a turn that genuinely cost nothing — printing `$0` for an unpriced turn states a number the
|
|
303
|
+
* store never had.
|
|
304
|
+
*/
|
|
305
|
+
costUsd: number | null;
|
|
306
|
+
}
|
|
307
|
+
/** A run of contiguous prose. `isStreaming` is the PART's own state, not the message's. */
|
|
308
|
+
interface TranscriptTextBlock {
|
|
309
|
+
kind: 'text';
|
|
310
|
+
key: string;
|
|
311
|
+
text: string;
|
|
312
|
+
isStreaming: boolean;
|
|
313
|
+
}
|
|
314
|
+
/** One file on a message — an uploaded attachment, or one the model produced. */
|
|
315
|
+
interface TranscriptFile {
|
|
316
|
+
/** Presigned or otherwise directly fetchable. Display-only: the model reads its own copy. */
|
|
317
|
+
url: string;
|
|
318
|
+
mediaType: string;
|
|
319
|
+
filename: string | null;
|
|
320
|
+
/** `image/*`, which a renderer can show inline rather than as a link. */
|
|
321
|
+
isImage: boolean;
|
|
322
|
+
}
|
|
323
|
+
/** A run of contiguous files. Grouped so a renderer can lay several out as one strip. */
|
|
324
|
+
interface TranscriptFilesBlock {
|
|
325
|
+
kind: 'files';
|
|
326
|
+
key: string;
|
|
327
|
+
files: TranscriptFile[];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* A run of contiguous reasoning. Carries its own disclosure state because a thread can hold many
|
|
331
|
+
* of them and each is toggled independently; `isOpen` defaults to `isStreaming` (thinking is worth
|
|
332
|
+
* watching live, worth folding away once answered) until `toggle` is called for that run.
|
|
333
|
+
*/
|
|
334
|
+
interface TranscriptReasoningBlock {
|
|
335
|
+
kind: 'reasoning';
|
|
336
|
+
key: string;
|
|
337
|
+
text: string;
|
|
338
|
+
isStreaming: boolean;
|
|
339
|
+
isOpen: boolean;
|
|
340
|
+
toggle: (open?: boolean) => void;
|
|
341
|
+
}
|
|
342
|
+
/** An action a run is waiting on a human for: approve, reject, answer, skip. */
|
|
343
|
+
interface TranscriptSettleState {
|
|
344
|
+
available: boolean;
|
|
345
|
+
/** True from the click until the run resumes and settles the call. */
|
|
346
|
+
isSubmitting: boolean;
|
|
347
|
+
run: () => void;
|
|
348
|
+
}
|
|
349
|
+
/** One call in a tool run, with whatever human decision it is parked on. */
|
|
350
|
+
interface TranscriptToolCall {
|
|
351
|
+
part: AnyToolUIPart;
|
|
352
|
+
toolCallId: string;
|
|
353
|
+
name: string;
|
|
354
|
+
/**
|
|
355
|
+
* Parked on a person. An `action` tool's input lands and its output never follows on its own —
|
|
356
|
+
* the loop waits for an approval between the two — so a settled-looking card that never settles
|
|
357
|
+
* IS the pending approval.
|
|
358
|
+
*/
|
|
359
|
+
isAwaitingApproval: boolean;
|
|
360
|
+
approve: TranscriptSettleState;
|
|
361
|
+
reject: TranscriptSettleState;
|
|
362
|
+
/** A failed decision — the call is still parked, so the affordance stays live. */
|
|
363
|
+
error: string | null;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* A run of CONSECUTIVE tool parts, grouped so a UI can collapse "5 tools ran" into one affordance.
|
|
367
|
+
* Any non-tool part between two tool parts ends the run — including the AI SDK's `step-start`
|
|
368
|
+
* markers, so tools from different steps never merge into one group.
|
|
369
|
+
*/
|
|
370
|
+
interface TranscriptToolBlock {
|
|
371
|
+
kind: 'tools';
|
|
372
|
+
key: string;
|
|
373
|
+
parts: AnyToolUIPart[];
|
|
374
|
+
/** The same calls, each with its human-decision state. Same order as `parts`. */
|
|
375
|
+
calls: TranscriptToolCall[];
|
|
376
|
+
}
|
|
377
|
+
/** One choice a question offers, with its live selection state. */
|
|
378
|
+
interface TranscriptQuestionOption {
|
|
379
|
+
value: string;
|
|
380
|
+
label: string;
|
|
381
|
+
/** A single character the request suggested as a shortcut; `null` when it suggested none. */
|
|
382
|
+
hotkey: string | null;
|
|
383
|
+
isSelected: boolean;
|
|
384
|
+
/** Pre-picked by the agent — what an untouched question submits as. */
|
|
385
|
+
isDefault: boolean;
|
|
386
|
+
/** Single choice: replaces the selection. Multiple: adds or removes this value. */
|
|
387
|
+
select: () => void;
|
|
388
|
+
}
|
|
389
|
+
/** One question of a set, numbered against the whole. */
|
|
390
|
+
interface TranscriptQuestion {
|
|
391
|
+
id: string;
|
|
392
|
+
prompt: string;
|
|
393
|
+
multiple: boolean;
|
|
394
|
+
/** 1-based. The request carries every question up front, so "Question 1 of N" is honest. */
|
|
395
|
+
position: number;
|
|
396
|
+
options: TranscriptQuestionOption[];
|
|
397
|
+
selected: string[];
|
|
398
|
+
/**
|
|
399
|
+
* The user has not touched this question, so a submission leaves it out and the server applies
|
|
400
|
+
* the same defaults it showed. Distinct from "selected happens to equal the defaults": only the
|
|
401
|
+
* first is recorded as `defaulted` rather than as a choice the user made.
|
|
402
|
+
*/
|
|
403
|
+
isPristine: boolean;
|
|
404
|
+
}
|
|
405
|
+
/** How a question set settled, once it did. */
|
|
406
|
+
interface TranscriptElicitationOutcome {
|
|
407
|
+
answers: Record<string, string[]>;
|
|
408
|
+
/** The user declined to answer and let the agent proceed on its own picks. */
|
|
409
|
+
skipped: boolean;
|
|
410
|
+
/** Questions filled from their own defaults rather than by the human. */
|
|
411
|
+
defaulted: string[];
|
|
412
|
+
/** The questions against the chosen labels, as the model read them back. */
|
|
413
|
+
summary: string | null;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* A question set the run put to the user, parked until someone settles it — the intake an agent
|
|
417
|
+
* declares and the model's own `ask` produce the same block, because the loop streams the same
|
|
418
|
+
* frame for both.
|
|
419
|
+
*/
|
|
420
|
+
interface TranscriptElicitationBlock {
|
|
421
|
+
kind: 'elicitation';
|
|
422
|
+
key: string;
|
|
423
|
+
/** The parked tool call — what `answer`/`skip` route by. */
|
|
424
|
+
toolCallId: string;
|
|
425
|
+
preamble: string | null;
|
|
426
|
+
questions: TranscriptQuestion[];
|
|
427
|
+
questionCount: number;
|
|
428
|
+
/** Still waiting on a human. */
|
|
429
|
+
isPending: boolean;
|
|
430
|
+
outcome: TranscriptElicitationOutcome | null;
|
|
431
|
+
/** A failed submission — the run is still parked, so the form stays live. */
|
|
432
|
+
error: string | null;
|
|
433
|
+
answer: TranscriptSettleState;
|
|
434
|
+
skip: TranscriptSettleState;
|
|
435
|
+
}
|
|
436
|
+
/** One retrieved passage, as it rides a retrieval tool call's output. Mirrors core's `Passage`. */
|
|
437
|
+
interface RetrievedPassage {
|
|
438
|
+
id: string;
|
|
439
|
+
text: string;
|
|
440
|
+
score: number;
|
|
441
|
+
/** Citation-facing origin — a document title, URL or row id. */
|
|
442
|
+
source?: string;
|
|
443
|
+
metadata?: Record<string, unknown>;
|
|
444
|
+
}
|
|
445
|
+
/** The passages of one origin, folded together so a citation line reads once per source. */
|
|
446
|
+
interface TranscriptSource {
|
|
447
|
+
/** The `source` string the retriever stamped, falling back to the passage id when it stamped none. */
|
|
448
|
+
id: string;
|
|
449
|
+
label: string;
|
|
450
|
+
passageCount: number;
|
|
451
|
+
/** Highest relevance among this source's passages; the scale is the retriever's. */
|
|
452
|
+
topScore: number;
|
|
453
|
+
passages: RetrievedPassage[];
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* The provenance behind an answer: a run of CONSECUTIVE retrieval tool parts, aggregated by origin.
|
|
457
|
+
* Detection is structural — a tool output shaped `{ passages: [{ id, text, ... }] }` — because the
|
|
458
|
+
* tool's NAME is not fixed: inject-mode retrieval persists as `retrieve`, and `createRetrievalTool`
|
|
459
|
+
* lets the host rename `search_knowledge` to anything.
|
|
460
|
+
*/
|
|
461
|
+
interface TranscriptSourcesBlock {
|
|
462
|
+
kind: 'sources';
|
|
463
|
+
key: string;
|
|
464
|
+
/** What was searched, when the tool call recorded a `{ query }` input. */
|
|
465
|
+
query: string | null;
|
|
466
|
+
sources: TranscriptSource[];
|
|
467
|
+
passageCount: number;
|
|
468
|
+
}
|
|
469
|
+
type TranscriptBlock = TranscriptTextBlock | TranscriptFilesBlock | TranscriptReasoningBlock | TranscriptToolBlock | TranscriptSourcesBlock | TranscriptElicitationBlock;
|
|
470
|
+
/** Where a question set's selection state is held, and where its settlement is sent. */
|
|
471
|
+
interface ElicitationBlockOptions {
|
|
472
|
+
/** The values a question is showing, or `undefined` while the user has not touched it. */
|
|
473
|
+
picked: (toolCallId: string, questionId: string) => string[] | undefined;
|
|
474
|
+
pick: (toolCallId: string, questionId: string, values: string[]) => void;
|
|
475
|
+
canAnswer: boolean;
|
|
476
|
+
canSkip: boolean;
|
|
477
|
+
answer: (toolCallId: string) => void;
|
|
478
|
+
skip: (toolCallId: string) => void;
|
|
479
|
+
isSubmitting: (toolCallId: string) => boolean;
|
|
480
|
+
errorOf: (toolCallId: string) => string | null;
|
|
481
|
+
}
|
|
482
|
+
/** Where an approval decision is sent, and what the last one did. */
|
|
483
|
+
interface ApprovalBlockOptions {
|
|
484
|
+
canApprove: boolean;
|
|
485
|
+
canReject: boolean;
|
|
486
|
+
approve: (toolCallId: string) => void;
|
|
487
|
+
reject: (toolCallId: string) => void;
|
|
488
|
+
isSubmitting: (toolCallId: string) => boolean;
|
|
489
|
+
errorOf: (toolCallId: string) => string | null;
|
|
490
|
+
}
|
|
491
|
+
interface BuildBlocksOptions {
|
|
492
|
+
/** Disclosure lookup for a reasoning run; `isStreaming` is the fallback when untouched. */
|
|
493
|
+
isReasoningOpen: (key: string, isStreaming: boolean) => boolean;
|
|
494
|
+
toggleReasoning: (key: string, open?: boolean) => void;
|
|
495
|
+
/**
|
|
496
|
+
* Lift retrieval tool parts out of the tool run into a `sources` block. Off by default, so a
|
|
497
|
+
* renderer wired to draw tool cards keeps receiving them as the tool calls they are.
|
|
498
|
+
*/
|
|
499
|
+
sources?: boolean;
|
|
500
|
+
/**
|
|
501
|
+
* Lift a question set out of the tool run into an `elicitation` block. Omitted → it stays a tool
|
|
502
|
+
* card, which is all a host that has nowhere to send an answer could render anyway.
|
|
503
|
+
*/
|
|
504
|
+
elicitation?: ElicitationBlockOptions;
|
|
505
|
+
/** Wire approve/reject onto the calls parked on a human. Omitted → they are reported, not actionable. */
|
|
506
|
+
approval?: ApprovalBlockOptions;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Walk a message's parts into renderable blocks, buffering consecutive tool parts into one run and
|
|
510
|
+
* consecutive files into one strip. Parts this library has no model for (`data-*`) are dropped
|
|
511
|
+
* rather than guessed at, but they still terminate a tool run — their position in the transcript is
|
|
512
|
+
* meaningful even when their content is not modelled here.
|
|
513
|
+
*/
|
|
514
|
+
declare function buildTranscriptBlocks(message: UIMessage, options: BuildBlocksOptions): TranscriptBlock[];
|
|
515
|
+
/** The message's prose, joined for the clipboard. Reasoning is excluded — it is not the answer. */
|
|
516
|
+
declare function extractMessageText(parts: UIMessage['parts'] | undefined): string;
|
|
517
|
+
interface UsageSummary extends MessageUsageInfo {
|
|
518
|
+
totalTokens: number;
|
|
519
|
+
/** `$0`, `$0.0123`, `$0.012`, `$1.23` — precision follows magnitude so sub-cent turns stay legible. */
|
|
520
|
+
costLabel: string;
|
|
521
|
+
tokensLabel: string;
|
|
522
|
+
}
|
|
523
|
+
declare function describeUsage(usage: MessageUsageInfo): UsageSummary;
|
|
524
|
+
interface TimestampInfo {
|
|
525
|
+
iso: string;
|
|
526
|
+
date: Date;
|
|
527
|
+
/** "just now" / "5 min. ago" / "Mar 3" — see {@link formatRelativeTime}. */
|
|
528
|
+
relative: string;
|
|
529
|
+
absolute: string;
|
|
530
|
+
}
|
|
531
|
+
/** `null` for an unparseable stamp, so a bad `created_at` renders nothing instead of "Invalid Date". */
|
|
532
|
+
declare function describeTimestamp(createdAt: string | null | undefined): TimestampInfo | null;
|
|
533
|
+
/**
|
|
534
|
+
* "just now" within ±10s (server `created_at` can land a few ms ahead of the client clock), a
|
|
535
|
+
* relative phrase up to a week, then a calendar date. Uses Intl.RelativeTimeFormat — no date-fns.
|
|
536
|
+
*/
|
|
537
|
+
declare function formatRelativeTime(date: Date): string;
|
|
538
|
+
|
|
539
|
+
interface StickToBottomOptions {
|
|
540
|
+
/**
|
|
541
|
+
* Changes whenever the rendered content grows — the signal to re-pin. A string rather than the
|
|
542
|
+
* message array because a streaming turn mutates in place: the array identity is stable while its
|
|
543
|
+
* last part's text keeps growing.
|
|
544
|
+
*/
|
|
545
|
+
contentKey: string;
|
|
546
|
+
/**
|
|
547
|
+
* How far off the bottom (px) still counts as "at the bottom". Sub-pixel layout and fractional
|
|
548
|
+
* device ratios leave a residue of a pixel or two after a programmatic scroll, so an exact
|
|
549
|
+
* comparison unpins the view on the very scroll event it just caused.
|
|
550
|
+
*/
|
|
551
|
+
threshold?: number;
|
|
552
|
+
}
|
|
553
|
+
interface StickToBottom {
|
|
554
|
+
/** While true, new content scrolls into view; the user scrolling up turns it off. */
|
|
555
|
+
isAtBottom: boolean;
|
|
556
|
+
/** Whether a "jump to latest" affordance is warranted. */
|
|
557
|
+
showJumpToLatest: boolean;
|
|
558
|
+
scrollToBottom: () => void;
|
|
559
|
+
/** Spread onto the scrolling element. Behaviour only — no classes, no styles. */
|
|
560
|
+
getContainerProps: () => {
|
|
561
|
+
ref: (element: HTMLElement | null) => void;
|
|
562
|
+
onScroll: () => void;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Follow a streaming transcript only while the reader is already at the bottom. Reading back
|
|
567
|
+
* through history during a stream is the case this exists for: an unconditional scroll-to-bottom
|
|
568
|
+
* yanks the viewport away mid-sentence on every token.
|
|
569
|
+
*/
|
|
570
|
+
declare function useStickToBottom({ contentKey, threshold, }: StickToBottomOptions): StickToBottom;
|
|
571
|
+
|
|
572
|
+
interface TranscriptCopyState {
|
|
573
|
+
/** False when the message has no prose to put on the clipboard. */
|
|
574
|
+
available: boolean;
|
|
575
|
+
copied: boolean;
|
|
576
|
+
copy: () => void;
|
|
577
|
+
}
|
|
578
|
+
interface TranscriptEditState {
|
|
579
|
+
available: boolean;
|
|
580
|
+
isEditing: boolean;
|
|
581
|
+
draft: string;
|
|
582
|
+
/** Whitespace-only drafts are refused, so an empty resubmit can never be sent. */
|
|
583
|
+
canSave: boolean;
|
|
584
|
+
start: () => void;
|
|
585
|
+
cancel: () => void;
|
|
586
|
+
setDraft: (value: string) => void;
|
|
587
|
+
save: () => void;
|
|
588
|
+
/** Controlled-textarea wiring: focus with the caret at the end, Enter saves, Escape cancels. */
|
|
589
|
+
getTextareaProps: () => {
|
|
590
|
+
ref: (element: HTMLTextAreaElement | null) => void;
|
|
591
|
+
value: string;
|
|
592
|
+
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
593
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
interface TranscriptActionState {
|
|
597
|
+
available: boolean;
|
|
598
|
+
run: () => void;
|
|
599
|
+
}
|
|
600
|
+
interface TranscriptItem {
|
|
601
|
+
id: string;
|
|
602
|
+
message: UIMessage;
|
|
603
|
+
role: UIMessage['role'];
|
|
604
|
+
/** Index in the FULL message array, not in the rendered window. */
|
|
605
|
+
index: number;
|
|
606
|
+
isUser: boolean;
|
|
607
|
+
isAssistant: boolean;
|
|
608
|
+
isLastAssistant: boolean;
|
|
609
|
+
isStreaming: boolean;
|
|
610
|
+
blocks: TranscriptBlock[];
|
|
611
|
+
/** The prose alone — what `copy` puts on the clipboard and what an edit starts from. */
|
|
612
|
+
text: string;
|
|
613
|
+
usage: UsageSummary | null;
|
|
614
|
+
timestamp: TimestampInfo | null;
|
|
615
|
+
copy: TranscriptCopyState;
|
|
616
|
+
edit: TranscriptEditState;
|
|
617
|
+
fork: TranscriptActionState;
|
|
618
|
+
regenerate: TranscriptActionState;
|
|
619
|
+
}
|
|
620
|
+
interface TranscriptItemOptions {
|
|
621
|
+
/** User messages get an inline edit-and-resubmit machine. */
|
|
622
|
+
editable?: boolean;
|
|
623
|
+
onEditSubmit?: (messageId: string, text: string) => void | Promise<void>;
|
|
624
|
+
onFork?: (messageId: string) => void | Promise<void>;
|
|
625
|
+
/** Only the LAST assistant message gets a regenerate machine. */
|
|
626
|
+
regeneratable?: boolean;
|
|
627
|
+
onRegenerate?: (messageId: string) => void | Promise<void>;
|
|
628
|
+
getUsage?: (message: UIMessage) => MessageUsageInfo | null;
|
|
629
|
+
getCreatedAt?: (message: UIMessage) => string | null;
|
|
630
|
+
/** How long `copy.copied` stays true. Default 1500ms. */
|
|
631
|
+
copyResetMs?: number;
|
|
632
|
+
/** Clipboard writer; defaults to `navigator.clipboard.writeText`. */
|
|
633
|
+
writeClipboard?: (text: string) => Promise<void>;
|
|
634
|
+
/**
|
|
635
|
+
* Lift retrieval tool parts into their own `sources` block instead of leaving them in the tool
|
|
636
|
+
* run. Off by default, so a renderer already drawing tool cards keeps drawing them.
|
|
637
|
+
*/
|
|
638
|
+
sources?: boolean;
|
|
639
|
+
/**
|
|
640
|
+
* Settle a parked question set — wire to `useAgentChat`'s `answer`. Supplying it is what lifts a
|
|
641
|
+
* question set out of the tool run into an `elicitation` block: without somewhere to send an
|
|
642
|
+
* answer, a form is a card the user cannot act on.
|
|
643
|
+
*
|
|
644
|
+
* `answers` holds ONLY the questions the user touched. An omitted one takes its own pre-picked
|
|
645
|
+
* default server-side, which is what keeps "just confirmed" and "picked exactly what was
|
|
646
|
+
* pre-picked" distinguishable in the settled row.
|
|
647
|
+
*/
|
|
648
|
+
onAnswer?: (toolCallId: string, answers: Record<string, string[]>) => void | Promise<void>;
|
|
649
|
+
/** Decline the question set and let the agent proceed on its own picks — `useAgentChat`'s `skip`. */
|
|
650
|
+
onSkip?: (toolCallId: string) => void | Promise<void>;
|
|
651
|
+
/** Settle a tool call parked on a human — `useAgentChat`'s `approve` / `reject`. */
|
|
652
|
+
onApprove?: (toolCallId: string) => void | Promise<void>;
|
|
653
|
+
onReject?: (toolCallId: string) => void | Promise<void>;
|
|
654
|
+
}
|
|
655
|
+
interface TranscriptWindow {
|
|
656
|
+
visibleCount: number;
|
|
657
|
+
/** How many older messages are not mounted. */
|
|
658
|
+
hiddenCount: number;
|
|
659
|
+
canLoadEarlier: boolean;
|
|
660
|
+
loadEarlier: () => void;
|
|
661
|
+
showAll: () => void;
|
|
662
|
+
}
|
|
663
|
+
interface TranscriptStopState {
|
|
664
|
+
available: boolean;
|
|
665
|
+
/** True from the click until the turn actually leaves a busy status. */
|
|
666
|
+
isStopping: boolean;
|
|
667
|
+
stop: () => void;
|
|
668
|
+
}
|
|
669
|
+
interface UseChatTranscriptOptions extends TranscriptItemOptions {
|
|
670
|
+
messages: UIMessage[];
|
|
671
|
+
status: ChatStatus;
|
|
672
|
+
initialVisibleCount?: number;
|
|
673
|
+
loadEarlierStep?: number;
|
|
674
|
+
/** Cancel the in-flight turn — wire to `useAgentChat`'s `cancel`. */
|
|
675
|
+
onStop?: () => void | Promise<void>;
|
|
676
|
+
/** Suggested follow-ups. The model only decides WHETHER they belong on screen. */
|
|
677
|
+
followUps?: string[] | null;
|
|
678
|
+
stickToBottomThreshold?: number;
|
|
679
|
+
}
|
|
680
|
+
interface ChatTranscript {
|
|
681
|
+
/** The mounted window, oldest first. `window.hiddenCount` says what is held back. */
|
|
682
|
+
items: TranscriptItem[];
|
|
683
|
+
status: ChatStatus;
|
|
684
|
+
isEmpty: boolean;
|
|
685
|
+
/** A turn is in flight: sent but not settled. */
|
|
686
|
+
isBusy: boolean;
|
|
687
|
+
isStreaming: boolean;
|
|
688
|
+
showEmptyState: boolean;
|
|
689
|
+
/** Waiting on the first token of a turn that has no assistant message yet. */
|
|
690
|
+
showTypingIndicator: boolean;
|
|
691
|
+
showFollowUps: boolean;
|
|
692
|
+
lastAssistantId: string | null;
|
|
693
|
+
window: TranscriptWindow;
|
|
694
|
+
scroll: StickToBottom;
|
|
695
|
+
stop: TranscriptStopState;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* The model behind a chat transcript: messages normalized into items with their parts grouped into
|
|
699
|
+
* runs, per-message derived values (copyable text, usage labels, relative time), each action as a
|
|
700
|
+
* state machine rather than a button, list windowing, and stick-to-bottom. It renders nothing and
|
|
701
|
+
* names no class — `MessageList`/`MessageItem` are one renderer over it, and an app with its own
|
|
702
|
+
* design system writes another without reimplementing any of this.
|
|
703
|
+
*/
|
|
704
|
+
declare function useChatTranscript(options: UseChatTranscriptOptions): ChatTranscript;
|
|
705
|
+
interface UseTranscriptItemOptions extends TranscriptItemOptions {
|
|
706
|
+
message: UIMessage;
|
|
707
|
+
isStreaming?: boolean;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* The single-message half of {@link useChatTranscript}, for a renderer that owns the list itself.
|
|
711
|
+
* `regeneratable` applies directly here: one assistant message is trivially the last one.
|
|
712
|
+
*/
|
|
713
|
+
declare function useTranscriptItem(options: UseTranscriptItemOptions): TranscriptItem;
|
|
714
|
+
|
|
715
|
+
type RenderToolPartFn = (part: AnyToolUIPart, key: string) => React.ReactNode;
|
|
716
|
+
/** Render a run of consecutive tool parts as one block (e.g. a collapsible tool group). */
|
|
717
|
+
type RenderToolGroupFn = (parts: AnyToolUIPart[], key: string) => React.ReactNode;
|
|
718
|
+
/** Render a message's text — pass a markdown renderer (e.g. `AgentMarkdown`) here if desired. */
|
|
719
|
+
type RenderTextFn = (text: string, ctx: {
|
|
720
|
+
isStreaming: boolean;
|
|
721
|
+
}) => React.ReactNode;
|
|
722
|
+
/** Render a run of files — attachments on a user turn, or files the model produced. */
|
|
723
|
+
type RenderFilesFn = (files: TranscriptFile[]) => React.ReactNode;
|
|
724
|
+
/** Render the body of a reasoning run; the disclosure toggle around it stays this component's. */
|
|
725
|
+
type RenderReasoningFn = (text: string, ctx: {
|
|
726
|
+
isStreaming: boolean;
|
|
727
|
+
isOpen: boolean;
|
|
728
|
+
}) => React.ReactNode;
|
|
729
|
+
interface MessageItemClassNames {
|
|
730
|
+
root?: string;
|
|
731
|
+
/** Resolved per role so user/assistant/system bubbles can differ. */
|
|
732
|
+
byRole?: Partial<Record<UIMessage['role'], string>>;
|
|
733
|
+
text?: string;
|
|
734
|
+
/** The action row under the bubble (copy / fork / regenerate / usage / timestamp). */
|
|
735
|
+
actions?: string;
|
|
736
|
+
actionButton?: string;
|
|
737
|
+
usage?: string;
|
|
738
|
+
timestamp?: string;
|
|
739
|
+
editTextarea?: string;
|
|
740
|
+
editActions?: string;
|
|
741
|
+
reasoning?: string;
|
|
742
|
+
reasoningToggle?: string;
|
|
743
|
+
reasoningText?: string;
|
|
744
|
+
files?: string;
|
|
745
|
+
file?: string;
|
|
746
|
+
fileImage?: string;
|
|
747
|
+
}
|
|
748
|
+
interface MessageRenderSlots {
|
|
749
|
+
renderToolPart?: RenderToolPartFn;
|
|
750
|
+
/** Takes precedence over `renderToolPart` for a run of consecutive tool parts. */
|
|
751
|
+
renderToolGroup?: RenderToolGroupFn;
|
|
752
|
+
renderText?: RenderTextFn;
|
|
753
|
+
renderReasoning?: RenderReasoningFn;
|
|
754
|
+
/** Draw files yourself — a gallery, a viewer. Omitted → images inline, everything else a link. */
|
|
755
|
+
renderFiles?: RenderFilesFn;
|
|
756
|
+
/**
|
|
757
|
+
* Extra content for the action row, after the built-in affordances and before usage — a badge
|
|
758
|
+
* naming which agent answered, a status pill. Rendered for every message that has an action row.
|
|
759
|
+
*/
|
|
760
|
+
meta?: React.ReactNode;
|
|
761
|
+
/** Label on the reasoning disclosure toggle. Default `"Reasoning"`. */
|
|
762
|
+
reasoningLabel?: React.ReactNode;
|
|
763
|
+
classNames?: MessageItemClassNames;
|
|
764
|
+
}
|
|
765
|
+
interface MessageItemProps extends MessageRenderSlots {
|
|
766
|
+
message: UIMessage;
|
|
767
|
+
/** Marks the in-flight assistant bubble (e.g. for a caret animation / deferred markdown render). */
|
|
768
|
+
isStreaming?: boolean;
|
|
769
|
+
/** User bubble gets a pencil → inline textarea; submitting calls back with the new text. */
|
|
770
|
+
editable?: boolean;
|
|
771
|
+
onEditSubmit?: (newText: string) => void | Promise<void>;
|
|
772
|
+
/** Assistant bubble gets a "Regenerate" affordance. */
|
|
773
|
+
regeneratable?: boolean;
|
|
774
|
+
onRegenerate?: () => void | Promise<void>;
|
|
775
|
+
/** When provided, a "Fork" affordance calls back with this message's id. */
|
|
776
|
+
onFork?: (uiMessageId: string) => void | Promise<void>;
|
|
777
|
+
/** Server-aggregated usage for this assistant turn (cost + tokens line). */
|
|
778
|
+
usage?: MessageUsageInfo | null;
|
|
779
|
+
/** ISO timestamp of the persisted message; rendered as a relative-time line when set. */
|
|
780
|
+
createdAt?: string | null;
|
|
781
|
+
}
|
|
782
|
+
interface MessageItemViewProps extends MessageRenderSlots {
|
|
783
|
+
item: TranscriptItem;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Styling-agnostic renderer for a single v7 `UIMessage`. Ports flip's full chat-item UX —
|
|
787
|
+
* copy, fork, regenerate, inline edit-and-resubmit, collapsible reasoning, usage line, relative
|
|
788
|
+
* timestamp, and tool grouping — but with ALL styling left to `classNames` and every action
|
|
789
|
+
* surfaced as a callback, so no design-system / icon / date library is pulled in (relative time
|
|
790
|
+
* uses Intl.RelativeTimeFormat).
|
|
791
|
+
*
|
|
792
|
+
* All of the logic lives in `useTranscriptItem`; this is one rendering of it. An app that wants
|
|
793
|
+
* different markup drives that hook and writes its own.
|
|
794
|
+
*/
|
|
795
|
+
declare function MessageItem({ message, isStreaming, editable, onEditSubmit, regeneratable, onRegenerate, onFork, usage, createdAt, ...slots }: MessageItemProps): react_jsx_runtime.JSX.Element;
|
|
796
|
+
/** The default markup for one modelled message — what `MessageItem` renders once it has an item. */
|
|
797
|
+
declare function MessageItemView({ item, ...slots }: MessageItemViewProps): react_jsx_runtime.JSX.Element;
|
|
798
|
+
|
|
799
|
+
interface MessageListClassNames {
|
|
800
|
+
root?: string;
|
|
801
|
+
empty?: string;
|
|
802
|
+
loadEarlier?: string;
|
|
803
|
+
typing?: string;
|
|
804
|
+
error?: string;
|
|
805
|
+
errorRetry?: string;
|
|
806
|
+
followUps?: string;
|
|
807
|
+
followUpChip?: string;
|
|
808
|
+
message?: MessageItemClassNames;
|
|
809
|
+
}
|
|
810
|
+
interface MessageListProps {
|
|
811
|
+
messages: UIMessage[];
|
|
812
|
+
status: ChatStatus;
|
|
813
|
+
renderToolPart?: RenderToolPartFn;
|
|
814
|
+
renderToolGroup?: RenderToolGroupFn;
|
|
815
|
+
renderText?: RenderTextFn;
|
|
816
|
+
/** Render the body of a reasoning run; the disclosure toggle is the list's. */
|
|
817
|
+
renderReasoning?: RenderReasoningFn;
|
|
818
|
+
/** Draw a message's files yourself. Omitted → images inline, everything else a link. */
|
|
819
|
+
renderFiles?: RenderFilesFn;
|
|
820
|
+
reasoningLabel?: React.ReactNode;
|
|
821
|
+
/** Extra content for one message's action row — e.g. a badge naming which agent answered. */
|
|
822
|
+
getMeta?: (message: UIMessage) => React.ReactNode;
|
|
823
|
+
/** When set, every message gets a "Fork" affordance calling back with its id. */
|
|
824
|
+
onFork?: (uiMessageId: string) => void | Promise<void>;
|
|
825
|
+
/** User messages get an inline edit-and-resubmit affordance. */
|
|
826
|
+
editable?: boolean;
|
|
827
|
+
onEditSubmit?: (uiMessageId: string, newText: string) => void | Promise<void>;
|
|
828
|
+
/** The last assistant message gets a "Regenerate" affordance. */
|
|
829
|
+
regeneratable?: boolean;
|
|
830
|
+
onRegenerate?: (uiMessageId: string) => void | Promise<void>;
|
|
831
|
+
/** Per-message resolvers for the usage line and the persisted timestamp. */
|
|
832
|
+
getUsage?: (message: UIMessage) => MessageUsageInfo | null;
|
|
833
|
+
getCreatedAt?: (message: UIMessage) => string | null;
|
|
834
|
+
/** Shown (with example/follow-up chips) when idle and empty. */
|
|
835
|
+
emptyState?: React.ReactNode;
|
|
836
|
+
/** Node rendered while waiting for the first assistant token. */
|
|
837
|
+
typingIndicator?: React.ReactNode;
|
|
838
|
+
/** AI-generated follow-up prompts under the last assistant message. */
|
|
839
|
+
followUps?: string[] | null;
|
|
840
|
+
onFollowUpClick?: (text: string) => void;
|
|
841
|
+
/** Last turn's error — paired with `onRetry`, renders a retry banner. */
|
|
842
|
+
error?: Error | null;
|
|
843
|
+
onRetry?: () => void | Promise<void>;
|
|
844
|
+
classNames?: MessageListClassNames;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Styling-agnostic message list. Keeps the proven UX — windowed render
|
|
848
|
+
* with "load earlier", a typing indicator, an inline error+retry banner,
|
|
849
|
+
* and follow-up chips — but ships no styles: drive everything through
|
|
850
|
+
* `classNames` and the `emptyState`/`typingIndicator` slots.
|
|
851
|
+
*
|
|
852
|
+
* Every decision here comes from `useChatTranscript`; the file below it is markup. A host that
|
|
853
|
+
* wants a different layout drives that hook directly and never imports this component.
|
|
854
|
+
*/
|
|
855
|
+
declare function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames, }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Languages the dictation mic offers. The Web Speech API expects a
|
|
859
|
+
* BCP-47 tag and does NOT auto-detect — the engine forces incoming
|
|
860
|
+
* audio onto the phonemes of whatever `recognition.lang` is set to, so
|
|
861
|
+
* the host app picks the locale explicitly.
|
|
862
|
+
*/
|
|
863
|
+
interface SpeechLanguage {
|
|
864
|
+
/** BCP-47 tag fed straight into `SpeechRecognition.lang`. */
|
|
865
|
+
tag: string;
|
|
866
|
+
/** Human label for a dropdown. */
|
|
867
|
+
label: string;
|
|
868
|
+
}
|
|
869
|
+
declare const DEFAULT_SPEECH_LANG = "en-US";
|
|
870
|
+
declare const SPEECH_LANGUAGES: ReadonlyArray<SpeechLanguage>;
|
|
871
|
+
/**
|
|
872
|
+
* Recover the last picked language from localStorage (sync — safe at
|
|
873
|
+
* `useState` init). Falls back to the default if nothing's stored or the
|
|
874
|
+
* stored tag isn't in the offered list.
|
|
875
|
+
*/
|
|
876
|
+
declare function loadStoredSpeechLang(): string;
|
|
877
|
+
declare function persistSpeechLang(tag: string): void;
|
|
878
|
+
|
|
879
|
+
interface UseSpeechRecognitionOptions {
|
|
880
|
+
lang?: string;
|
|
881
|
+
onFinalTranscript: (text: string) => void;
|
|
882
|
+
onInterimTranscript?: (text: string) => void;
|
|
883
|
+
}
|
|
884
|
+
interface SpeechRecognitionHook {
|
|
885
|
+
isSupported: boolean;
|
|
886
|
+
isListening: boolean;
|
|
887
|
+
start(): void;
|
|
888
|
+
stop(): void;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Tiny wrapper around the Web Speech API. `start()` opens the mic and
|
|
892
|
+
* streams interim transcripts via `onInterimTranscript`; on a finalized
|
|
893
|
+
* chunk it fires `onFinalTranscript`. `isSupported` is false where the
|
|
894
|
+
* API is missing (e.g. Firefox) — callers should hide the mic button.
|
|
895
|
+
*/
|
|
896
|
+
declare function useSpeechRecognition({ lang, onFinalTranscript, onInterimTranscript, }: UseSpeechRecognitionOptions): SpeechRecognitionHook;
|
|
897
|
+
|
|
898
|
+
/** One completion a source offers. */
|
|
899
|
+
interface AutocompleteItem {
|
|
900
|
+
/** Unique within its source. */
|
|
901
|
+
id: string;
|
|
902
|
+
/** What the reader sees, and what the default filter matches against. */
|
|
903
|
+
label: string;
|
|
904
|
+
/** What replaces the typed token. Defaults to `label` — set it when the label is prose. */
|
|
905
|
+
value?: string;
|
|
906
|
+
description?: string;
|
|
907
|
+
/**
|
|
908
|
+
* A short trailing note a renderer right-aligns — where this came from, a keyboard shortcut, a
|
|
909
|
+
* type. Deliberately outside the default filter: typing a query should narrow by what the item
|
|
910
|
+
* IS, not by an aside about it.
|
|
911
|
+
*/
|
|
912
|
+
hint?: string;
|
|
913
|
+
/** Whatever the host's renderer needs: an icon key, a scope, the row this came from. */
|
|
914
|
+
data?: unknown;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Where a trigger character is allowed to open the menu.
|
|
918
|
+
*
|
|
919
|
+
* - `'start'` — only as the very first character of the input. This is the rule a slash command
|
|
920
|
+
* wants: `/deploy` is a command, `src/foo` and `and/or` are not.
|
|
921
|
+
* - `'word'` — at the start of any word: the input's start, or immediately after whitespace. This
|
|
922
|
+
* is the rule a mention wants: `@ada` in the middle of a sentence is a mention, `ada@example` is
|
|
923
|
+
* an address.
|
|
924
|
+
*
|
|
925
|
+
* There is no default on purpose. Which rule a trigger takes is the difference between an
|
|
926
|
+
* autocomplete that feels invisible and one that fires while somebody types a file path.
|
|
927
|
+
*/
|
|
928
|
+
type TriggerPosition = 'start' | 'word';
|
|
929
|
+
interface AutocompleteSource {
|
|
930
|
+
/** Identifies the source to the host; also names the listbox when `label` is omitted. */
|
|
931
|
+
id: string;
|
|
932
|
+
/** Human-facing name for the open menu. */
|
|
933
|
+
label?: string;
|
|
934
|
+
/** The single character that opens this source. Two sources never share one. */
|
|
935
|
+
trigger: string;
|
|
936
|
+
position: TriggerPosition;
|
|
937
|
+
/**
|
|
938
|
+
* The candidates for `query`. May be async; a result that arrives after the query moved on is
|
|
939
|
+
* discarded, and `signal` aborts the request that produced it. Throwing is allowed — the menu
|
|
940
|
+
* reports the failure and typing carries on.
|
|
941
|
+
*/
|
|
942
|
+
getItems: (query: string, signal: AbortSignal) => AutocompleteItem[] | Promise<AutocompleteItem[]>;
|
|
943
|
+
/**
|
|
944
|
+
* Narrow what `getItems` returned. Defaults to {@link filterAutocompleteItems}. A source that
|
|
945
|
+
* already filtered server-side — a fuzzy match this one would undo — passes `(items) => items`.
|
|
946
|
+
*/
|
|
947
|
+
filter?: (items: AutocompleteItem[], query: string) => AutocompleteItem[];
|
|
948
|
+
/**
|
|
949
|
+
* Appended after the inserted value. Defaults to a single space, which both closes the menu and
|
|
950
|
+
* puts the caret where the command's argument goes.
|
|
951
|
+
*/
|
|
952
|
+
insertSuffix?: string;
|
|
953
|
+
}
|
|
954
|
+
/** A trigger character found before the caret, with the token being typed after it. */
|
|
955
|
+
interface TriggerMatch {
|
|
956
|
+
source: AutocompleteSource;
|
|
957
|
+
/** Index of the trigger character itself. */
|
|
958
|
+
index: number;
|
|
959
|
+
/** The text between the trigger and the caret. Never contains whitespace. */
|
|
960
|
+
query: string;
|
|
961
|
+
}
|
|
962
|
+
interface CompletionEdit {
|
|
963
|
+
text: string;
|
|
964
|
+
caret: number;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* The trigger token the caret currently sits in, or `null`. Scans backwards from the caret and
|
|
968
|
+
* stops at the first whitespace, so a token is always one unbroken word: typing a space is what
|
|
969
|
+
* ends a command and hands the rest of the line back to the user as prose.
|
|
970
|
+
*/
|
|
971
|
+
declare function findActiveTrigger(text: string, caret: number, sources: readonly AutocompleteSource[]): TriggerMatch | null;
|
|
972
|
+
/** Case-insensitive substring over label and description. The default when a source names none. */
|
|
973
|
+
declare function filterAutocompleteItems(items: readonly AutocompleteItem[], query: string): AutocompleteItem[];
|
|
974
|
+
/**
|
|
975
|
+
* The text and caret after accepting `item`. The trigger character stays — `/deploy ` is what the
|
|
976
|
+
* user meant to type — and everything after the caret is left untouched, so completing a token in
|
|
977
|
+
* the middle of a sentence does not eat the rest of it.
|
|
978
|
+
*/
|
|
979
|
+
declare function applyCompletion(text: string, caret: number, match: TriggerMatch, item: AutocompleteItem): CompletionEdit;
|
|
980
|
+
|
|
981
|
+
/** What each item carries on `data`, so a renderer can show where a skill came from. */
|
|
982
|
+
interface SkillSuggestionData {
|
|
983
|
+
/** The scope token it resolved from — the provenance a user is entitled to see. */
|
|
984
|
+
scope: string;
|
|
985
|
+
/** Scope tokens of same-named skills this one outranks, widest last. */
|
|
986
|
+
shadows?: string[];
|
|
987
|
+
}
|
|
988
|
+
interface SkillsSourceOptions {
|
|
989
|
+
client: AgentClient;
|
|
990
|
+
/**
|
|
991
|
+
* Read at query time, not captured once: a threadless chat learns its id only when the backend
|
|
992
|
+
* creates one on the first send, and a skill may be scoped to a conversation.
|
|
162
993
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
994
|
+
getThreadId?: () => string | undefined;
|
|
995
|
+
/** Default `/`. */
|
|
996
|
+
trigger?: string;
|
|
997
|
+
/** Default `Skills`. */
|
|
998
|
+
label?: string;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* `GET /agent/skills` as an autocomplete source — the concrete one behind `/`, and an example of
|
|
1002
|
+
* the shape any other takes. `position: 'start'` because a skill is a command: `/deploy` is one and
|
|
1003
|
+
* `src/foo` is a path.
|
|
1004
|
+
*/
|
|
1005
|
+
declare function createSkillsSource(options: SkillsSourceOptions): AutocompleteSource;
|
|
1006
|
+
|
|
1007
|
+
interface UseComposerAutocompleteOptions {
|
|
1008
|
+
/** The composer's draft. Accepting an item rewrites it through `onValueChange`. */
|
|
1009
|
+
value: string;
|
|
1010
|
+
onValueChange: (next: string) => void;
|
|
1011
|
+
/**
|
|
1012
|
+
* What can be completed, and after which character. The composer knows nothing about what a
|
|
1013
|
+
* source offers — a skills list and an agent mention are the same shape.
|
|
1014
|
+
*/
|
|
1015
|
+
sources: readonly AutocompleteSource[];
|
|
1016
|
+
/** A source that threw. The menu reports it; typing is unaffected either way. */
|
|
1017
|
+
onError?: (error: unknown, source: AutocompleteSource) => void;
|
|
1018
|
+
/** Tab accepts the highlighted item, as Enter does. Default `true`. */
|
|
1019
|
+
acceptOnTab?: boolean;
|
|
1020
|
+
/** Base for the generated `id`s the ARIA wiring points at. */
|
|
1021
|
+
id?: string;
|
|
1022
|
+
}
|
|
1023
|
+
interface AutocompleteInputProps {
|
|
1024
|
+
ref: (element: HTMLTextAreaElement | HTMLInputElement | null) => void;
|
|
1025
|
+
onChange: (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
1026
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
1027
|
+
onSelect: (event: React.SyntheticEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
1028
|
+
onClick: (event: React.MouseEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
|
|
1029
|
+
role: 'combobox';
|
|
1030
|
+
'aria-expanded': boolean;
|
|
1031
|
+
'aria-autocomplete': 'list';
|
|
1032
|
+
'aria-haspopup': 'listbox';
|
|
1033
|
+
'aria-controls'?: string;
|
|
1034
|
+
'aria-activedescendant'?: string;
|
|
177
1035
|
}
|
|
1036
|
+
interface AutocompleteListboxProps {
|
|
1037
|
+
id: string;
|
|
1038
|
+
role: 'listbox';
|
|
1039
|
+
'aria-label': string;
|
|
1040
|
+
}
|
|
1041
|
+
interface AutocompleteOptionProps {
|
|
1042
|
+
id: string;
|
|
1043
|
+
role: 'option';
|
|
1044
|
+
'aria-selected': boolean;
|
|
1045
|
+
}
|
|
1046
|
+
interface ComposerAutocomplete {
|
|
1047
|
+
isOpen: boolean;
|
|
1048
|
+
/** The source behind the open menu, so a renderer can group or label by it. */
|
|
1049
|
+
source: AutocompleteSource | null;
|
|
1050
|
+
trigger: string | null;
|
|
1051
|
+
/** The text between the trigger and the caret. */
|
|
1052
|
+
query: string;
|
|
1053
|
+
items: AutocompleteItem[];
|
|
1054
|
+
activeIndex: number;
|
|
1055
|
+
activeItem: AutocompleteItem | null;
|
|
1056
|
+
/** An async source has been asked and has not answered yet. */
|
|
1057
|
+
isLoading: boolean;
|
|
1058
|
+
/** The message from a source that threw. The menu stays open and empty. */
|
|
1059
|
+
error: string | null;
|
|
1060
|
+
highlight: (index: number) => void;
|
|
1061
|
+
/** Wraps at both ends, so ↓ from the last item lands on the first. */
|
|
1062
|
+
moveHighlight: (delta: number) => void;
|
|
1063
|
+
/** Insert an item — the highlighted one by default. A click handler calls this directly. */
|
|
1064
|
+
accept: (item?: AutocompleteItem) => void;
|
|
1065
|
+
/** Close for the token being typed, without touching the text. */
|
|
1066
|
+
dismiss: () => void;
|
|
1067
|
+
getInputProps: () => AutocompleteInputProps;
|
|
1068
|
+
getListboxProps: () => AutocompleteListboxProps;
|
|
1069
|
+
getOptionProps: (index: number) => AutocompleteOptionProps;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* The state machine behind a `/`-style autocomplete in a chat composer: which trigger the caret is
|
|
1073
|
+
* inside, what the query is, which items answer it, which one is highlighted, and what accepting
|
|
1074
|
+
* one does to the text and the caret. It renders nothing and knows nothing about skills, agents or
|
|
1075
|
+
* threads — a host supplies sources, and the same machine drives `@` as drives `/`.
|
|
1076
|
+
*
|
|
1077
|
+
* The keys the open menu owns (↑ ↓ Enter Tab Escape) are consumed with `preventDefault`, which is
|
|
1078
|
+
* how a composer that sends on Enter knows to stand down: call this handler first and return early
|
|
1079
|
+
* when the event comes back `defaultPrevented`.
|
|
1080
|
+
*/
|
|
1081
|
+
declare function useComposerAutocomplete(options: UseComposerAutocompleteOptions): ComposerAutocomplete;
|
|
178
1082
|
|
|
179
1083
|
/**
|
|
180
1084
|
* Convert a persisted `StoredMessage` (the agent-lib's on-disk shape) into an AI SDK v7
|
|
@@ -326,6 +1230,34 @@ interface UseAgentChatOptions {
|
|
|
326
1230
|
runId: string;
|
|
327
1231
|
status: 'completed' | 'failed';
|
|
328
1232
|
}) => void;
|
|
1233
|
+
/**
|
|
1234
|
+
* Track DETACHED sub-agents this conversation started: which are still working, and their answers
|
|
1235
|
+
* as they land (see {@link ChatBackground}). Default `false`.
|
|
1236
|
+
*
|
|
1237
|
+
* Opt-in for the same reason {@link UseAgentChatOptions.resume} is — it reads the thread, and a
|
|
1238
|
+
* plain mount must not spend a request on a feature the host is not using. Leaving it off still
|
|
1239
|
+
* leaves {@link ChatBackground.refresh} callable, for a host that would rather drive it itself.
|
|
1240
|
+
*/
|
|
1241
|
+
background?: boolean;
|
|
1242
|
+
/**
|
|
1243
|
+
* How often to re-read the thread while a detached sub-agent is still working, in ms. Default
|
|
1244
|
+
* 5000; `0` turns the interval off, leaving {@link ChatBackground.refresh} as the only trigger.
|
|
1245
|
+
*
|
|
1246
|
+
* A poll rather than a second stream, because what a background run produces is a persisted
|
|
1247
|
+
* MESSAGE, not tokens this chat should render as they arrive: its answer belongs to its own run,
|
|
1248
|
+
* it can land while the user is mid-turn or has the tab closed, and it has to be there on the next
|
|
1249
|
+
* reload either way. The interval exists only while something is outstanding, which is rare and
|
|
1250
|
+
* bounded by how many delegations a conversation has started.
|
|
1251
|
+
*/
|
|
1252
|
+
backgroundPollMs?: number;
|
|
1253
|
+
}
|
|
1254
|
+
/** Sub-agents this conversation started and did not wait for. See {@link BackgroundRun}. */
|
|
1255
|
+
interface ChatBackground {
|
|
1256
|
+
runs: BackgroundRun[];
|
|
1257
|
+
/** True while any of them is still working — what a "2 agents running" affordance reads. */
|
|
1258
|
+
isWorking: boolean;
|
|
1259
|
+
/** Re-read the thread now, instead of waiting for the next poll. */
|
|
1260
|
+
refresh: () => Promise<void>;
|
|
329
1261
|
}
|
|
330
1262
|
interface AddToolResultArgs {
|
|
331
1263
|
tool: string;
|
|
@@ -339,7 +1271,28 @@ interface AddToolResultArgs {
|
|
|
339
1271
|
* `AgentClient`. Mirrors flip's `useAdminChat`, generalized.
|
|
340
1272
|
*/
|
|
341
1273
|
declare function useAgentChat(options: UseAgentChatOptions): {
|
|
1274
|
+
sendMessage: (message?: (Omit<UIMessage<unknown, ai.UIDataTypes, ai.UITools>, "id" | "role"> & {
|
|
1275
|
+
id?: string;
|
|
1276
|
+
role?: "system" | "user" | "assistant";
|
|
1277
|
+
} & {
|
|
1278
|
+
text?: never;
|
|
1279
|
+
files?: never;
|
|
1280
|
+
messageId?: string;
|
|
1281
|
+
}) | {
|
|
1282
|
+
text: string;
|
|
1283
|
+
files?: FileList | ai.FileUIPart[];
|
|
1284
|
+
metadata?: unknown;
|
|
1285
|
+
parts?: never;
|
|
1286
|
+
messageId?: string;
|
|
1287
|
+
} | {
|
|
1288
|
+
files: FileList | ai.FileUIPart[];
|
|
1289
|
+
metadata?: unknown;
|
|
1290
|
+
parts?: never;
|
|
1291
|
+
messageId?: string;
|
|
1292
|
+
} | undefined, options?: ai.ChatRequestOptions) => Promise<void>;
|
|
342
1293
|
addToolResult: ({ tool, toolCallId, output }: AddToolResultArgs) => void | PromiseLike<void>;
|
|
1294
|
+
/** Sub-agents this conversation started and did not wait for. */
|
|
1295
|
+
background: ChatBackground;
|
|
343
1296
|
runId: string | undefined;
|
|
344
1297
|
/** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
|
|
345
1298
|
activeRunId: string | null;
|
|
@@ -362,221 +1315,24 @@ declare function useAgentChat(options: UseAgentChatOptions): {
|
|
|
362
1315
|
toolCallId: string;
|
|
363
1316
|
reason?: string;
|
|
364
1317
|
}) => Promise<void>;
|
|
1318
|
+
answer: ({ toolCallId, answers, }: {
|
|
1319
|
+
toolCallId: string;
|
|
1320
|
+
answers?: Record<string, string[]>;
|
|
1321
|
+
}) => Promise<void>;
|
|
1322
|
+
skip: ({ toolCallId }: {
|
|
1323
|
+
toolCallId: string;
|
|
1324
|
+
}) => Promise<void>;
|
|
365
1325
|
regenerate: () => void;
|
|
366
1326
|
id: string;
|
|
367
1327
|
setMessages: (messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[] | ((messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[]) => UIMessage<unknown, ai.UIDataTypes, ai.UITools>[])) => void;
|
|
368
1328
|
error: Error | undefined;
|
|
369
1329
|
stop: () => Promise<void>;
|
|
370
|
-
messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[];
|
|
371
1330
|
status: ai.ChatStatus;
|
|
372
|
-
|
|
373
|
-
id?: string;
|
|
374
|
-
role?: "user" | "assistant" | "system";
|
|
375
|
-
} & {
|
|
376
|
-
text?: never;
|
|
377
|
-
files?: never;
|
|
378
|
-
messageId?: string;
|
|
379
|
-
}) | {
|
|
380
|
-
text: string;
|
|
381
|
-
files?: FileList | ai.FileUIPart[];
|
|
382
|
-
metadata?: unknown;
|
|
383
|
-
parts?: never;
|
|
384
|
-
messageId?: string;
|
|
385
|
-
} | {
|
|
386
|
-
files: FileList | ai.FileUIPart[];
|
|
387
|
-
metadata?: unknown;
|
|
388
|
-
parts?: never;
|
|
389
|
-
messageId?: string;
|
|
390
|
-
} | undefined, options?: ai.ChatRequestOptions) => Promise<void>;
|
|
1331
|
+
messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[];
|
|
391
1332
|
resumeStream: (options?: ai.ChatRequestOptions) => Promise<void>;
|
|
392
1333
|
addToolOutput: ai.ChatAddToolOutputFunction<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>;
|
|
393
1334
|
addToolApprovalResponse: ai.ChatAddToolApproveResponseFunction;
|
|
394
1335
|
clearError: () => void;
|
|
395
1336
|
};
|
|
396
1337
|
|
|
397
|
-
|
|
398
|
-
onSubmit: (text: string) => void;
|
|
399
|
-
disabled?: boolean;
|
|
400
|
-
placeholder?: string;
|
|
401
|
-
rows?: number;
|
|
402
|
-
/** Enable the dictation mic button (Web Speech API). Default `true`. */
|
|
403
|
-
enableSpeech?: boolean;
|
|
404
|
-
/** BCP-47 locale for dictation. Default `en-US`. */
|
|
405
|
-
speechLang?: string;
|
|
406
|
-
/** Label/content for the send button. Default `"Send"`. */
|
|
407
|
-
sendLabel?: React.ReactNode;
|
|
408
|
-
/** Wrapper class. The component ships no styles of its own. */
|
|
409
|
-
className?: string;
|
|
410
|
-
textareaClassName?: string;
|
|
411
|
-
sendButtonClassName?: string;
|
|
412
|
-
micButtonClassName?: string;
|
|
413
|
-
/**
|
|
414
|
-
* Custom mic affordance. Receives `isListening`; return any node. When
|
|
415
|
-
* omitted a plain text button ("Speak" / "Stop") is rendered.
|
|
416
|
-
*/
|
|
417
|
-
renderMic?: (state: {
|
|
418
|
-
isListening: boolean;
|
|
419
|
-
}) => React.ReactNode;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Styling-agnostic chat composer. Enter submits, Shift+Enter inserts a
|
|
423
|
-
* newline. Optional dictation streams interim transcripts into the draft
|
|
424
|
-
* and commits finalized chunks. No design-system or icon-library deps —
|
|
425
|
-
* style it entirely through the `*ClassName` props.
|
|
426
|
-
*/
|
|
427
|
-
declare function ChatInput({ onSubmit, disabled, placeholder, rows, enableSpeech, speechLang, sendLabel, className, textareaClassName, sendButtonClassName, micButtonClassName, renderMic, }: ChatInputProps): react_jsx_runtime.JSX.Element;
|
|
428
|
-
|
|
429
|
-
/** A tool UI part on a `UIMessage` — a static `tool-*` part or the `dynamic-tool` part. */
|
|
430
|
-
type AnyToolUIPart = ToolUIPart | DynamicToolUIPart;
|
|
431
|
-
type RenderToolPartFn = (part: AnyToolUIPart, key: string) => React.ReactNode;
|
|
432
|
-
/** Render a run of consecutive tool parts as one block (e.g. a collapsible tool group). */
|
|
433
|
-
type RenderToolGroupFn = (parts: AnyToolUIPart[], key: string) => React.ReactNode;
|
|
434
|
-
/** Render a message's text — pass a markdown renderer (e.g. `AgentMarkdown`) here if desired. */
|
|
435
|
-
type RenderTextFn = (text: string, ctx: {
|
|
436
|
-
isStreaming: boolean;
|
|
437
|
-
}) => React.ReactNode;
|
|
438
|
-
/** Server-aggregated usage for an assistant turn, shown as a small inline line. */
|
|
439
|
-
interface MessageUsageInfo {
|
|
440
|
-
inputTokens: number;
|
|
441
|
-
outputTokens: number;
|
|
442
|
-
costUsd: number;
|
|
443
|
-
}
|
|
444
|
-
interface MessageItemClassNames {
|
|
445
|
-
root?: string;
|
|
446
|
-
/** Resolved per role so user/assistant/system bubbles can differ. */
|
|
447
|
-
byRole?: Partial<Record<UIMessage['role'], string>>;
|
|
448
|
-
text?: string;
|
|
449
|
-
/** The action row under the bubble (copy / fork / regenerate / usage / timestamp). */
|
|
450
|
-
actions?: string;
|
|
451
|
-
actionButton?: string;
|
|
452
|
-
usage?: string;
|
|
453
|
-
timestamp?: string;
|
|
454
|
-
editTextarea?: string;
|
|
455
|
-
editActions?: string;
|
|
456
|
-
}
|
|
457
|
-
interface MessageItemProps {
|
|
458
|
-
message: UIMessage;
|
|
459
|
-
renderToolPart?: RenderToolPartFn;
|
|
460
|
-
/** Takes precedence over `renderToolPart` for a run of consecutive tool parts. */
|
|
461
|
-
renderToolGroup?: RenderToolGroupFn;
|
|
462
|
-
renderText?: RenderTextFn;
|
|
463
|
-
classNames?: MessageItemClassNames;
|
|
464
|
-
/** Marks the in-flight assistant bubble (e.g. for a caret animation / deferred markdown render). */
|
|
465
|
-
isStreaming?: boolean;
|
|
466
|
-
/** User bubble gets a pencil → inline textarea; submitting calls back with the new text. */
|
|
467
|
-
editable?: boolean;
|
|
468
|
-
onEditSubmit?: (newText: string) => void | Promise<void>;
|
|
469
|
-
/** Assistant bubble gets a "Regenerate" affordance. */
|
|
470
|
-
regeneratable?: boolean;
|
|
471
|
-
onRegenerate?: () => void | Promise<void>;
|
|
472
|
-
/** When provided, a "Fork" affordance calls back with this message's id. */
|
|
473
|
-
onFork?: (uiMessageId: string) => void | Promise<void>;
|
|
474
|
-
/** Server-aggregated usage for this assistant turn (cost + tokens line). */
|
|
475
|
-
usage?: MessageUsageInfo | null;
|
|
476
|
-
/** ISO timestamp of the persisted message; rendered as a relative-time line when set. */
|
|
477
|
-
createdAt?: string | null;
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Styling-agnostic renderer for a single v7 `UIMessage`. Ports flip's full chat-item UX —
|
|
481
|
-
* copy, fork, regenerate, inline edit-and-resubmit, usage line, relative timestamp, and tool
|
|
482
|
-
* grouping — but with ALL styling left to `classNames` and every action surfaced as a callback,
|
|
483
|
-
* so no design-system / icon / date library is pulled in (relative time uses Intl.RelativeTimeFormat).
|
|
484
|
-
*/
|
|
485
|
-
declare function MessageItem({ message, renderToolPart, renderToolGroup, renderText, classNames, isStreaming, editable, onEditSubmit, regeneratable, onRegenerate, onFork, usage, createdAt, }: MessageItemProps): react_jsx_runtime.JSX.Element;
|
|
486
|
-
/**
|
|
487
|
-
* "just now" within ±10s (server `created_at` can land a few ms ahead of the client clock), a
|
|
488
|
-
* relative phrase up to a week, then a calendar date. Uses Intl.RelativeTimeFormat — no date-fns.
|
|
489
|
-
*/
|
|
490
|
-
declare function formatRelativeTime(date: Date): string;
|
|
491
|
-
|
|
492
|
-
type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
|
|
493
|
-
interface MessageListClassNames {
|
|
494
|
-
root?: string;
|
|
495
|
-
empty?: string;
|
|
496
|
-
loadEarlier?: string;
|
|
497
|
-
typing?: string;
|
|
498
|
-
error?: string;
|
|
499
|
-
errorRetry?: string;
|
|
500
|
-
followUps?: string;
|
|
501
|
-
followUpChip?: string;
|
|
502
|
-
message?: MessageItemClassNames;
|
|
503
|
-
}
|
|
504
|
-
interface MessageListProps {
|
|
505
|
-
messages: UIMessage[];
|
|
506
|
-
status: ChatStatus;
|
|
507
|
-
renderToolPart?: RenderToolPartFn;
|
|
508
|
-
renderToolGroup?: RenderToolGroupFn;
|
|
509
|
-
renderText?: RenderTextFn;
|
|
510
|
-
/** When set, every message gets a "Fork" affordance calling back with its id. */
|
|
511
|
-
onFork?: (uiMessageId: string) => void | Promise<void>;
|
|
512
|
-
/** User messages get an inline edit-and-resubmit affordance. */
|
|
513
|
-
editable?: boolean;
|
|
514
|
-
onEditSubmit?: (uiMessageId: string, newText: string) => void | Promise<void>;
|
|
515
|
-
/** The last assistant message gets a "Regenerate" affordance. */
|
|
516
|
-
regeneratable?: boolean;
|
|
517
|
-
onRegenerate?: (uiMessageId: string) => void | Promise<void>;
|
|
518
|
-
/** Per-message resolvers for the usage line and the persisted timestamp. */
|
|
519
|
-
getUsage?: (message: UIMessage) => MessageUsageInfo | null;
|
|
520
|
-
getCreatedAt?: (message: UIMessage) => string | null;
|
|
521
|
-
/** Shown (with example/follow-up chips) when idle and empty. */
|
|
522
|
-
emptyState?: React.ReactNode;
|
|
523
|
-
/** Node rendered while waiting for the first assistant token. */
|
|
524
|
-
typingIndicator?: React.ReactNode;
|
|
525
|
-
/** AI-generated follow-up prompts under the last assistant message. */
|
|
526
|
-
followUps?: string[] | null;
|
|
527
|
-
onFollowUpClick?: (text: string) => void;
|
|
528
|
-
/** Last turn's error — paired with `onRetry`, renders a retry banner. */
|
|
529
|
-
error?: Error | null;
|
|
530
|
-
onRetry?: () => void | Promise<void>;
|
|
531
|
-
classNames?: MessageListClassNames;
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Styling-agnostic message list. Keeps the proven UX — windowed render
|
|
535
|
-
* with "load earlier", a typing indicator, an inline error+retry banner,
|
|
536
|
-
* and follow-up chips — but ships no styles: drive everything through
|
|
537
|
-
* `classNames` and the `emptyState`/`typingIndicator` slots.
|
|
538
|
-
*/
|
|
539
|
-
declare function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames, }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Languages the dictation mic offers. The Web Speech API expects a
|
|
543
|
-
* BCP-47 tag and does NOT auto-detect — the engine forces incoming
|
|
544
|
-
* audio onto the phonemes of whatever `recognition.lang` is set to, so
|
|
545
|
-
* the host app picks the locale explicitly.
|
|
546
|
-
*/
|
|
547
|
-
interface SpeechLanguage {
|
|
548
|
-
/** BCP-47 tag fed straight into `SpeechRecognition.lang`. */
|
|
549
|
-
tag: string;
|
|
550
|
-
/** Human label for a dropdown. */
|
|
551
|
-
label: string;
|
|
552
|
-
}
|
|
553
|
-
declare const DEFAULT_SPEECH_LANG = "en-US";
|
|
554
|
-
declare const SPEECH_LANGUAGES: ReadonlyArray<SpeechLanguage>;
|
|
555
|
-
/**
|
|
556
|
-
* Recover the last picked language from localStorage (sync — safe at
|
|
557
|
-
* `useState` init). Falls back to the default if nothing's stored or the
|
|
558
|
-
* stored tag isn't in the offered list.
|
|
559
|
-
*/
|
|
560
|
-
declare function loadStoredSpeechLang(): string;
|
|
561
|
-
declare function persistSpeechLang(tag: string): void;
|
|
562
|
-
|
|
563
|
-
interface UseSpeechRecognitionOptions {
|
|
564
|
-
lang?: string;
|
|
565
|
-
onFinalTranscript: (text: string) => void;
|
|
566
|
-
onInterimTranscript?: (text: string) => void;
|
|
567
|
-
}
|
|
568
|
-
interface SpeechRecognitionHook {
|
|
569
|
-
isSupported: boolean;
|
|
570
|
-
isListening: boolean;
|
|
571
|
-
start(): void;
|
|
572
|
-
stop(): void;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Tiny wrapper around the Web Speech API. `start()` opens the mic and
|
|
576
|
-
* streams interim transcripts via `onInterimTranscript`; on a finalized
|
|
577
|
-
* chunk it fires `onFinalTranscript`. `isSupported` is false where the
|
|
578
|
-
* API is missing (e.g. Firefox) — callers should hide the mic button.
|
|
579
|
-
*/
|
|
580
|
-
declare function useSpeechRecognition({ lang, onFinalTranscript, onInterimTranscript, }: UseSpeechRecognitionOptions): SpeechRecognitionHook;
|
|
581
|
-
|
|
582
|
-
export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AggregatedTurnUsage, type AnyToolUIPart, type CancelResult, ChatInput, type ChatInputProps, type ChatStatus, DEFAULT_SPEECH_LANG, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, SPEECH_LANGUAGES, type SpeechLanguage, type SpeechRecognitionHook, type StoredTurnMetadata, type ThreadPatch, type UseAgentChatOptions, type UseSpeechRecognitionOptions, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, storedMessageToUiMessage, storedThreadToUiMessages, useAgentChat, useSpeechRecognition };
|
|
1338
|
+
export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AggregatedTurnUsage, type AnyToolUIPart, type ApprovalBlockOptions, type AutocompleteInputProps, type AutocompleteItem, type AutocompleteListboxProps, type AutocompleteOptionProps, type AutocompleteSource, type BackgroundRun, type BuildBlocksOptions, type CancelResult, type ChatBackground, ChatInput, type ChatInputProps, type ChatStatus, type ChatTranscript, type CompletionEdit, type ComposerAutocomplete, DEFAULT_SPEECH_LANG, type ElicitationBlockOptions, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageItemView, type MessageItemViewProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderFilesFn, type RenderReasoningFn, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, type RetrievedPassage, SPEECH_LANGUAGES, type SkillSuggestionData, type SkillsSourceOptions, type SpeechLanguage, type SpeechRecognitionHook, type StickToBottom, type StickToBottomOptions, type StoredTurnMetadata, type ThreadPatch, type TimestampInfo, type TranscriptActionState, type TranscriptBlock, type TranscriptCopyState, type TranscriptEditState, type TranscriptElicitationBlock, type TranscriptElicitationOutcome, type TranscriptFile, type TranscriptFilesBlock, type TranscriptItem, type TranscriptItemOptions, type TranscriptQuestion, type TranscriptQuestionOption, type TranscriptReasoningBlock, type TranscriptSettleState, type TranscriptSource, type TranscriptSourcesBlock, type TranscriptStopState, type TranscriptTextBlock, type TranscriptToolBlock, type TranscriptToolCall, type TranscriptWindow, type TriggerMatch, type TriggerPosition, type UsageSummary, type UseAgentChatOptions, type UseChatTranscriptOptions, type UseComposerAutocompleteOptions, type UseSpeechRecognitionOptions, type UseTranscriptItemOptions, applyCompletion, backgroundRunsFromThread, buildTranscriptBlocks, createSkillsSource, describeTimestamp, describeUsage, extractMessageText, filterAutocompleteItems, findActiveTrigger, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, storedMessageToUiMessage, storedThreadToUiMessages, useAgentChat, useChatTranscript, useComposerAutocomplete, useSpeechRecognition, useStickToBottom, useTranscriptItem };
|