@deuz-sdk/react 2.2.0 → 2.3.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 +71 -10
- package/dist/index.cjs +360 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -8
- package/dist/index.d.ts +84 -8
- package/dist/index.js +361 -83
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,9 @@ export function Chat() {
|
|
|
16
16
|
<>
|
|
17
17
|
{messages.map((m) => (
|
|
18
18
|
<div key={m.id}>
|
|
19
|
-
{m.parts
|
|
19
|
+
{m.parts
|
|
20
|
+
? m.parts.map((p, i) => (p.type === 'text' ? <span key={i}>{p.text}</span> : null))
|
|
21
|
+
: m.content}
|
|
20
22
|
</div>
|
|
21
23
|
))}
|
|
22
24
|
<button disabled={status === 'streaming'} onClick={() => sendMessage({ text: 'Hello!' })}>
|
|
@@ -27,7 +29,9 @@ export function Chat() {
|
|
|
27
29
|
}
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
Thin adapter by design. Every chat-state transformation lives in `@deuz-sdk/core/chat` (pure reducers, branch helpers) and the Deuz UI wire in `@deuz-sdk/core/ui`; this package only binds them to React state, which is why a refresh, a network blip and a server crash all look the same to your component. `react` is a peer dependency (`^18 || ^19`). The legacy `@deuz-sdk/core/react` subpath still works but is frozen
|
|
32
|
+
Thin adapter by design. Every chat-state transformation lives in `@deuz-sdk/core/chat` (pure reducers, branch helpers) and the Deuz UI wire in `@deuz-sdk/core/ui`; this package only binds them to React state, which is why a refresh, a network blip and a server crash all look the same to your component. `react` is a peer dependency (`^18 || ^19`), and so is `@deuz-sdk/core` (`^2.0.0`); native agent routes, `agent`, `stopDetails` and `outcome` need core 2.3, whose UI wire carries them. The legacy `@deuz-sdk/core/react` subpath still works but is frozen: it gets none of 2.3's fixes and no native agent routes. New work lands here.
|
|
33
|
+
|
|
34
|
+
**New in 2.3.** `useChat` drives a [native agent route](#native-agent-routes-23) with no new option and reports the run as `agent`, the provider's explanation of a stop as `stopDetails`, and how each tool call ended as its `outcome`. Approvals and client tools work the same way on every path: a verdict counts only when it answers the open gate, a turn read back through `reconnect()` runs its client calls like a streamed one, and the history the hook posts answers every call the server ran or approved. [What 2.3 changed](#what-23-changed-in-usechat) lists each change; the [React hooks guide](https://github.com/Deuz-AI/Deuz-SDK/blob/main/docs/content/docs/modules/react-hooks.mdx) has the details.
|
|
31
35
|
|
|
32
36
|
## `useChat(options)`
|
|
33
37
|
|
|
@@ -35,35 +39,90 @@ Thin adapter by design. Every chat-state transformation lives in `@deuz-sdk/core
|
|
|
35
39
|
|
|
36
40
|
| Option | Notes |
|
|
37
41
|
| --- | --- |
|
|
38
|
-
| `api` | Endpoint that returns a Deuz stream. Required. |
|
|
42
|
+
| `api` | Endpoint that returns a Deuz stream: `toDeuzStreamResponse` for a chat route, or `toDeuzAgentStreamResponse` for a native agent route (2.3). Required. |
|
|
39
43
|
| `initialMessages?` | Canonical `Message[]`, projected once at mount via `uiFromMessages`. |
|
|
40
44
|
| `headers?` / `body?` | Merged into every request. |
|
|
41
45
|
| `chatId?` | Sent with every request; the server side keys persistence on it. |
|
|
42
46
|
| `resume?` | `{ endpoint, lastEventId?, auto?, cursor? }` — enables `reconnect()`, and with `auto` reconnects on mount after a reload. |
|
|
43
47
|
| `throttleMs?` | Coalesce re-renders during a fast stream. |
|
|
44
|
-
| `onToolCall?` | Client-tool executor. The round-trip and its error self-healing are handled for you. |
|
|
45
|
-
| `onData?` | Custom `data` parts from the server. |
|
|
48
|
+
| `onToolCall?` | Client-tool executor. The round-trip and its error self-healing are handled for you. Omit it to park client calls in `pendingToolCalls` and answer them with `addToolResult`. A result that lands after the chat moved on is dropped (2.3). |
|
|
49
|
+
| `onData?` | Custom `data` parts from the server. A native route's agent frames never reach it. |
|
|
46
50
|
| `onError?` / `onHttpError?` | `onHttpError: 'error-part'` (default) surfaces a non-2xx response on the stream instead of throwing. |
|
|
47
51
|
| `generateId?` / `fetch?` | Injection seams, same idea as core's. |
|
|
48
52
|
|
|
53
|
+
2.3 adds no option.
|
|
54
|
+
|
|
49
55
|
**State**
|
|
50
56
|
|
|
51
|
-
`messages` (`UIMessage[]`, ordered parts), `history` (`{ ui, canonical }`), `status`, `error`, `pendingApprovals`, `pendingToolCalls`, `cost`, `budgetExceeded`, `dataParts`, `citations`, `plan`, `activity`, `verifications`, `warnings`, `falseFinishes`, `subAgents`, `steps`, `usage`, `finishReason`.
|
|
57
|
+
`messages` (`UIMessage[]`, ordered parts), `history` (`{ ui, canonical }`), `status`, `error`, `pendingApprovals`, `pendingToolCalls`, `cost`, `budgetExceeded`, `dataParts`, `citations`, `plan`, `activity`, `verifications`, `warnings`, `falseFinishes`, `subAgents`, `steps`, `usage`, `finishReason`, and since 2.3 `stopDetails` and `agent`.
|
|
58
|
+
|
|
59
|
+
- `stopDetails` — the provider's explanation of the turn's stop, from the wire's `finish`: with `finishReason: 'content_filter'`, a refusal's `category` and `explanation`. Absent when the provider explained nothing.
|
|
60
|
+
- `agent` — the native run behind the turn, on a route that serves `toDeuzAgentStreamResponse`: its `runId`, its `status` (`'running'`, `'suspended'`, `'completed'`, `'stopped'` or `'failed'`), the latest unvalidated `partial` draft while it runs, and the `output` the server validated, only once it is `completed`. A `stopped` run carries its `reason`. Absent on a chat route.
|
|
61
|
+
- Each `UIToolCall` in `messages` carries `outcome` since 2.3, from the latest `tool-state` or `tool-result` frame: `'tool-error'`, `'permission-denied'`, `'approval-denied'`, `'approval-pending'` or `'unknown-effect'`. It tells a guardrail block from an approver's no, which `denied` alone does not, and is absent while the call runs and after it succeeds.
|
|
52
62
|
|
|
53
63
|
Most of those exist because the agentic loop reports more than text: an approval it is waiting on, a plan it is following, a sub-agent it delegated to, the cost so far. Render what you need and ignore the rest.
|
|
54
64
|
|
|
55
65
|
**Methods**
|
|
56
66
|
|
|
57
|
-
- `sendMessage(input)` — `input` is a `ChatInput`: `{ text }`,
|
|
67
|
+
- `sendMessage(input)` — `input` is a `ChatInput`: a string, or `{ text?, parts? }` with files as canonical parts. `partsFromFiles(files)`, exported next to the hook, turns picked files into those parts.
|
|
58
68
|
- `stop()`, `regenerate()`, `editAndResend(messageId, input)` — the last two branch history through core's `dropTrailingAssistant` / `branchBeforeUserMessage`.
|
|
59
|
-
- `addToolApprovalResponse(response)` — carries the request's signed token automatically, and resumes the run once every pending approval has a verdict.
|
|
60
|
-
- `
|
|
69
|
+
- `addToolApprovalResponse(response)` — carries the request's signed token automatically, and resumes the run once every pending approval has a verdict. Since 2.3 a verdict that answers no approval in `pendingApprovals` (nothing pending, a double click, a stale card, another run's token) is a no-op that sends nothing.
|
|
70
|
+
- `addToolResult({ toolCallId, output, isError? })` — answers one parked client tool call; the chat continues once every call in `pendingToolCalls` has a result. A result for an id that is not parked is ignored.
|
|
71
|
+
- `setHistory(update)` / `setMessages(update)` — writable state for optimistic UI and hand-editing. Both drop the pending gates and, since 2.3, the in-memory resume cursor.
|
|
61
72
|
- `reconnect()` — re-attaches to an interrupted stream through `connectDeuzStream`.
|
|
62
73
|
- `clearError()`.
|
|
63
74
|
|
|
75
|
+
## Native agent routes (2.3)
|
|
76
|
+
|
|
77
|
+
`useChat` drives a route that serves a native run (`streamAgent` / `resumeStreamAgent` from `@deuz-sdk/core/agent`) through `toDeuzAgentStreamResponse` from `@deuz-sdk/core/ui`. Nothing is configured on the client. The run's terminal frame decides what it waits for: its approvals pause the chat, and its client calls (tools without `execute` on the server) run through `onToolCall` or park in `pendingToolCalls`. Once every approval has a verdict, the hook runs the client calls and resumes the run in one request, `{ messages, runId, approvalResponses?, clientToolResults? }`, because a native resume proceeds only with every verdict and every client result. `status` is `'streaming'` while those calls run, and `stop()` cancels the round.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useChat } from '@deuz-sdk/react';
|
|
81
|
+
|
|
82
|
+
export function AgentChat() {
|
|
83
|
+
const { messages, sendMessage, pendingApprovals, addToolApprovalResponse, agent, stopDetails } =
|
|
84
|
+
useChat({
|
|
85
|
+
api: '/api/agent', // serves toDeuzAgentStreamResponse(streamAgent(...))
|
|
86
|
+
onToolCall: async ({ toolName }) => (toolName === 'locate' ? { city: 'Paris' } : null),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
{messages.map((m) => (
|
|
92
|
+
<p key={m.id}>{m.content}</p>
|
|
93
|
+
))}
|
|
94
|
+
{pendingApprovals.map((request) => (
|
|
95
|
+
<button
|
|
96
|
+
key={request.approvalId}
|
|
97
|
+
onClick={() => addToolApprovalResponse({ approvalId: request.approvalId, approved: true })}
|
|
98
|
+
>
|
|
99
|
+
Approve {request.toolName}
|
|
100
|
+
</button>
|
|
101
|
+
))}
|
|
102
|
+
{agent?.status === 'completed' ? <output>{JSON.stringify(agent.output)}</output> : null}
|
|
103
|
+
{agent?.status === 'stopped' && agent.reason === 'refusal' ? (
|
|
104
|
+
<p role="alert">Refused: {stopDetails?.category ?? 'no reason given'}</p>
|
|
105
|
+
) : null}
|
|
106
|
+
<button onClick={() => sendMessage('Publish document 7.')}>Send</button>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The route starts a run for a request without `runId` and resumes the stored run for one with it; take the run's scope from your authentication, never from the body. A chat route never sends an agent frame, so the hook never sends it `runId` or `clientToolResults`. The [React hooks guide](https://github.com/Deuz-AI/Deuz-SDK/blob/main/docs/content/docs/modules/react-hooks.mdx) has a complete route.
|
|
113
|
+
|
|
114
|
+
## What 2.3 changed in `useChat`
|
|
115
|
+
|
|
116
|
+
- **Verdicts answer the open gate.** A verdict must name an `approvalId` in `pendingApprovals`, and a `token` on it must be that request's token. Anything else sends nothing and changes no state. A repeated `tool-approval-request` frame is one pending approval.
|
|
117
|
+
- **A reconnected turn settles like a streamed one.** A turn read back through `reconnect()` or `resume.auto` hands its unresolved client calls to `onToolCall` (and the chat continues with a POST) or parks them in `pendingToolCalls`. A replayed call runs at most once per mounted hook. Across a reload, only a result in the restored `history.canonical` shows that a call ran, so make such tools idempotent.
|
|
118
|
+
- **The history answers every call.** A call the server ran, or ran after an approval, is answered in `history.canonical` by a `role: 'tool'` message right after the assistant turn that made it, holding the result the wire carried, so the next request carries no open `tool_use`. `validateChatRequest` accepts these results with its defaults.
|
|
119
|
+
- **The cursor follows the POST read.** When a route passes `store` and `streamId`, the journal `id:` lines of the POST response move the in-memory resume cursor, so a `reconnect()` after a settled turn appends nothing twice. The persisted `resume.cursor` still moves on resume reads only.
|
|
120
|
+
- **Late client results are dropped.** An `onToolCall` result that lands after a new message, `regenerate`, `editAndResend`, `setHistory` or `setMessages` no longer joins the new exchange.
|
|
121
|
+
- **More of the turn arrives.** `stopDetails`, each tool call's `outcome`, and a Gemini thought signature on plain text, which the hook sends back with the history (not live-verified). Two responses that share a message id (a route without `messageId`) fill a bubble each.
|
|
122
|
+
|
|
64
123
|
## `useObject<T>(options)`
|
|
65
124
|
|
|
66
|
-
Streams `toDeuzObjectStreamResponse` output. Options: `api`, `headers?`, `fetch?`. Returns `object` (the latest `DeepPartial<T>`), `isLoading`, `error`, `submit(input)`, `stop()`.
|
|
125
|
+
Streams `toDeuzObjectStreamResponse` output. Options: `api`, `headers?`, `onHttpError?`, `throttleMs?`, `fetch?`. Returns `object` (the latest `DeepPartial<T>`), `isLoading`, `error`, `submit(input)`, `stop()`.
|
|
67
126
|
|
|
68
127
|
## Headless components
|
|
69
128
|
|
|
@@ -76,4 +135,6 @@ Zero styling, both overridable.
|
|
|
76
135
|
|
|
77
136
|
`UIMessage`, `UIMessagePart`, `UIToolCall`, `AssistantTurnState`, `ChatHistory`, `ChatInput`, `DeuzUIPart`, `Message`, `ToolApprovalRequest`, `ToolApprovalResponse`.
|
|
78
137
|
|
|
138
|
+
The package's own types: `UseChatOptions`, `UseChatResult`, `UseChatCost`, `UseChatBudgetExceeded`, `UseChatResumeOptions`, `UseChatCursorStore`, `UseObjectOptions`, `UseObjectResult`, `ToolApprovalCardProps`, `CostBadgeProps`, and since 2.3 `UseChatAgent` and `UseChatStopDetails`, derived from the installed core's types.
|
|
139
|
+
|
|
79
140
|
Full SDK tour, architecture and the honest limitations list: [github.com/Deuz-AI/Deuz-SDK](https://github.com/Deuz-AI/Deuz-SDK#readme).
|