chat-nest-sdk 1.0.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 ADDED
@@ -0,0 +1,103 @@
1
+ # chat-nest-sdk
2
+
3
+ > Frontend SDK for Chat Nest providing a simple React hook to consume streaming AI APIs safely.
4
+
5
+ This package handles:
6
+ - Streaming response handling
7
+ - Cancellation propagation
8
+ - Intelligent retry behavior
9
+ - Error normalization
10
+ - Message state management
11
+
12
+ Designed for production usage in React applications.
13
+
14
+ ---
15
+
16
+ ## ✨ Features
17
+
18
+ - Streaming token handling
19
+ - Abort-safe cancellation
20
+ - Retry only on network / server failures
21
+ - No retries on client or policy errors
22
+ - Message state management
23
+ - Lightweight and framework-friendly
24
+
25
+ ---
26
+
27
+ ## 📦 Installation
28
+
29
+ ```bash
30
+ npm install chat-nest-sdk
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 🚀 Usage
36
+
37
+ ### Example
38
+
39
+ ```
40
+ import { useAiChat } from "chat-nest-sdk";
41
+
42
+ function App() {
43
+ const chat = useAiChat({
44
+ endpoint: "http://localhost:3001/api/chat",
45
+ });
46
+
47
+ return (
48
+ <>
49
+ <div>
50
+ {chat.messages.map((m) => (
51
+ <div key={m.id}>
52
+ <strong>{m.role}</strong>: {m.content}
53
+ </div>
54
+ ))}
55
+ </div>
56
+
57
+ <button onClick={() => chat.sendMessage("Hello!")}>
58
+ Send
59
+ </button>
60
+
61
+ <button onClick={chat.cancel}>
62
+ Cancel
63
+ </button>
64
+ </>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 🧠 API
72
+
73
+ ### useAiChat(options)
74
+
75
+ | Field | Type | Description |
76
+ | -------- | ------ | -------------------- |
77
+ | endpoint | string | Backend API endpoint |
78
+
79
+ | Field | Description |
80
+ | ----------------- | ------------------------ |
81
+ | messages | Chat message list |
82
+ | sendMessage(text) | Sends user message |
83
+ | cancel() | Cancels active request |
84
+ | isStreaming | Whether stream is active |
85
+ | error | Last error |
86
+
87
+ ---
88
+
89
+ ## ⚠️ Important Notes
90
+
91
+ Only one active request is allowed at a time.
92
+
93
+ Cancel immediately stops streaming and billing.
94
+
95
+ 4xx errors are never retried.
96
+
97
+ Network failures retry automatically.
98
+
99
+ ---
100
+
101
+ ## 📄 License
102
+
103
+ ISC
@@ -0,0 +1,16 @@
1
+ import { UseAIChatOptions, UseAIChatReturn, AIClient, AiClientConfig, Message, StreamCallbacks } from 'chat-nest-core';
2
+
3
+ declare function useAiChat(options: UseAIChatOptions): UseAIChatReturn;
4
+
5
+ declare class FetchAiClient implements AIClient {
6
+ private abortController?;
7
+ private config;
8
+ constructor(config: AiClientConfig);
9
+ streamChat(messages: Message[], callbacks: StreamCallbacks): Promise<void>;
10
+ cancel(): void;
11
+ private executeStream;
12
+ private normalizeError;
13
+ private backoff;
14
+ }
15
+
16
+ export { FetchAiClient, useAiChat };
package/dist/index.js ADDED
@@ -0,0 +1,195 @@
1
+ // src/react/useAiChat.ts
2
+ import { useCallback, useMemo, useRef, useState } from "react";
3
+ import {
4
+ MessageRole
5
+ } from "chat-nest-core";
6
+
7
+ // src/core/aiClient.ts
8
+ var DEFAULT_TIMEOUT = 3e4;
9
+ var DEFAULT_RETRIES = 2;
10
+ var NonRetryableError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "NonRetryableError";
14
+ }
15
+ };
16
+ var FetchAiClient = class {
17
+ constructor(config) {
18
+ this.config = {
19
+ endpoint: config.endpoint,
20
+ headers: config.headers ?? {},
21
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT,
22
+ maxRetries: config.maxRetries ?? DEFAULT_RETRIES
23
+ };
24
+ }
25
+ async streamChat(messages, callbacks) {
26
+ let attempt = 0;
27
+ while (attempt <= this.config.maxRetries) {
28
+ try {
29
+ await this.executeStream(messages, callbacks);
30
+ return;
31
+ } catch (error) {
32
+ if (error?.name === "AbortError") {
33
+ return;
34
+ }
35
+ if (error instanceof Error && error.name === "NonRetryableError") {
36
+ callbacks.onError(this.normalizeError(error));
37
+ return;
38
+ }
39
+ attempt++;
40
+ if (attempt > this.config.maxRetries) {
41
+ callbacks.onError(this.normalizeError(error));
42
+ return;
43
+ }
44
+ await this.backoff(attempt);
45
+ }
46
+ }
47
+ }
48
+ cancel() {
49
+ this.abortController?.abort();
50
+ }
51
+ async executeStream(messages, callbacks) {
52
+ this.abortController = new AbortController();
53
+ const timeoutId = setTimeout(
54
+ () => this.abortController?.abort(),
55
+ this.config.timeoutMs
56
+ );
57
+ try {
58
+ const response = await fetch(this.config.endpoint, {
59
+ method: "POST",
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ ...this.config.headers
63
+ },
64
+ signal: this.abortController.signal,
65
+ body: JSON.stringify({ messages })
66
+ });
67
+ if (!response.ok) {
68
+ if (response.status >= 400 && response.status < 500) {
69
+ throw new NonRetryableError(`HTTP ${response.status}`);
70
+ }
71
+ throw new Error(`HTTP ${response.status}`);
72
+ }
73
+ if (!response.body) {
74
+ throw new Error("Streaming not supported by the response");
75
+ }
76
+ const reader = response.body.getReader();
77
+ const decoder = new TextDecoder();
78
+ while (true) {
79
+ const { value, done } = await reader.read();
80
+ if (done) break;
81
+ const chunk = decoder.decode(value, { stream: true });
82
+ callbacks.onToken(chunk);
83
+ }
84
+ callbacks.onComplete();
85
+ } finally {
86
+ clearTimeout(timeoutId);
87
+ }
88
+ }
89
+ normalizeError(error) {
90
+ if (error?.name === "AbortError") {
91
+ return new Error("Request cancelled by user");
92
+ }
93
+ if (error instanceof Error) {
94
+ return error;
95
+ }
96
+ return new Error("Unknown AI client error");
97
+ }
98
+ async backoff(attempt) {
99
+ const delay = Math.min(1e3 * attempt, 3e3);
100
+ return new Promise((resolve) => setTimeout(resolve, delay));
101
+ }
102
+ };
103
+
104
+ // src/core/utils/helpers.ts
105
+ function generateId() {
106
+ return crypto.randomUUID();
107
+ }
108
+
109
+ // src/react/useAiChat.ts
110
+ function useAiChat(options) {
111
+ const {
112
+ endpoint,
113
+ initialMessages = [],
114
+ maxMessages = 10
115
+ } = options;
116
+ const [messages, setMessages] = useState(
117
+ initialMessages
118
+ );
119
+ const [isStreaming, setIsStreaming] = useState(false);
120
+ const [error, setError] = useState();
121
+ const clientRef = useRef(null);
122
+ if (!clientRef.current) {
123
+ clientRef.current = new FetchAiClient({
124
+ endpoint
125
+ });
126
+ }
127
+ const sendMessage = useCallback(async (text) => {
128
+ if (!text.trim() || isStreaming) return;
129
+ setError(void 0);
130
+ const userMessage = {
131
+ id: generateId(),
132
+ role: MessageRole.User,
133
+ content: text
134
+ };
135
+ const assistantMessage = {
136
+ id: generateId(),
137
+ role: MessageRole.Assistant,
138
+ content: ""
139
+ };
140
+ setMessages((prev) => {
141
+ const next = [...prev, userMessage, assistantMessage];
142
+ return next.slice(-maxMessages);
143
+ });
144
+ setIsStreaming(true);
145
+ const history = [...messages, userMessage];
146
+ try {
147
+ await clientRef.current.streamChat(history, {
148
+ onToken(token) {
149
+ setMessages(
150
+ (prev) => prev.map(
151
+ (msg) => msg.id === assistantMessage.id ? {
152
+ ...msg,
153
+ content: msg.content + token
154
+ } : msg
155
+ )
156
+ );
157
+ },
158
+ onComplete() {
159
+ setIsStreaming(false);
160
+ },
161
+ onError(err) {
162
+ setError(err.message);
163
+ setIsStreaming(false);
164
+ }
165
+ });
166
+ } catch (err) {
167
+ setError(err.message);
168
+ setIsStreaming(false);
169
+ }
170
+ }, [endpoint, isStreaming, maxMessages, messages]);
171
+ const cancel = useCallback(() => {
172
+ clientRef.current?.cancel();
173
+ setIsStreaming(false);
174
+ }, []);
175
+ const reset = useCallback(() => {
176
+ cancel();
177
+ setMessages(initialMessages);
178
+ setError(void 0);
179
+ }, [cancel, initialMessages]);
180
+ return useMemo(
181
+ () => ({
182
+ messages,
183
+ sendMessage,
184
+ cancel,
185
+ reset,
186
+ isStreaming,
187
+ error
188
+ }),
189
+ [messages, sendMessage, cancel, reset, isStreaming, error]
190
+ );
191
+ }
192
+ export {
193
+ FetchAiClient,
194
+ useAiChat
195
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "chat-nest-sdk",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "homepage": "https://github.com/shivams10/chat-nest",
8
+ "bugs": {
9
+ "url": "https://github.com/shivams10/chat-nest/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/shivams10/chat-nest"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm --dts --external react --external react-dom",
20
+ "dev": "tsup src/index.ts --format esm --watch --external react --external react-dom",
21
+ "clean": "rm -rf dist",
22
+ "test": "echo \"Error: no test specified\" && exit 1"
23
+ },
24
+ "keywords": [],
25
+ "author": "Shivam Shukla",
26
+ "license": "ISC",
27
+ "description": "",
28
+ "sideEffects": false,
29
+ "devDependencies": {
30
+ "@types/react": "^19.2.8",
31
+ "tsup": "^8.5.1",
32
+ "typescript": "^5.9.3"
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=18",
36
+ "react-dom": ">=18"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "import": "./dist/index.js",
41
+ "types": "./dist/index.d.ts"
42
+ }
43
+ },
44
+ "dependencies": {
45
+ "chat-nest-core": "*"
46
+ }
47
+ }