@ragbits/api-client 0.0.3 → 1.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 +51 -101
- package/__tests__/mocks/handlers.ts +98 -0
- package/__tests__/setup.ts +23 -0
- package/__tests__/utils.ts +45 -0
- package/dist/autogen.types.d.ts +426 -0
- package/dist/index.cjs +147 -51
- package/dist/index.d.ts +14 -5
- package/dist/index.js +146 -51
- package/dist/types.d.ts +37 -198
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -25,6 +25,12 @@ import { RagbitsClient } from '@ragbits/api-client'
|
|
|
25
25
|
// Initialize the client
|
|
26
26
|
const client = new RagbitsClient({
|
|
27
27
|
baseUrl: 'http://127.0.0.1:8000', // Optional, defaults to http://127.0.0.1:8000
|
|
28
|
+
auth: {
|
|
29
|
+
getToken: () => 'token',
|
|
30
|
+
onUnauthorized: () => {
|
|
31
|
+
console.warn('⚠️ Unauthorized')
|
|
32
|
+
},
|
|
33
|
+
}, // Optional, used to provide auth details
|
|
28
34
|
})
|
|
29
35
|
|
|
30
36
|
// Get API configuration
|
|
@@ -80,6 +86,7 @@ new RagbitsClient(config?: ClientConfig)
|
|
|
80
86
|
**Parameters:**
|
|
81
87
|
|
|
82
88
|
- `config.baseUrl` (optional): Base URL for the API. Defaults to 'http://127.0.0.1:8000'
|
|
89
|
+
- `config.auth` (optional): An object containing authentication details. Provide `getToken` to automatically attach `Authorization: Bearer <token>` to every request. `onUnauthorized` is called if the library encounters a 401 status code.
|
|
83
90
|
|
|
84
91
|
**Throws:** `Error` if the base URL is invalid
|
|
85
92
|
|
|
@@ -91,13 +98,14 @@ Get the base URL used by this client.
|
|
|
91
98
|
|
|
92
99
|
**Returns:** The configured base URL
|
|
93
100
|
|
|
94
|
-
##### `makeRequest<
|
|
101
|
+
##### `async makeRequest<Endpoints, URL>(endpoint, options?): Promise<Result>`
|
|
95
102
|
|
|
96
|
-
Make a type-safe API request to
|
|
103
|
+
Make a type-safe API request to any endpoint. `endpoint` must be one of the key of `Endpoints` type
|
|
104
|
+
that define schema of the available endpoints. All default Ragbits routes are supported out of the box.
|
|
97
105
|
|
|
98
106
|
**Parameters:**
|
|
99
107
|
|
|
100
|
-
- `endpoint`:
|
|
108
|
+
- `endpoint`: API endpoint path (e.g., '/api/config', '/api/feedback')
|
|
101
109
|
- `options` (optional): Request options
|
|
102
110
|
- `method`: HTTP method (defaults to endpoint's predefined method)
|
|
103
111
|
- `body`: Request body (typed based on endpoint)
|
|
@@ -121,15 +129,39 @@ const feedback = await client.makeRequest('/api/feedback', {
|
|
|
121
129
|
payload: { rating: 5 },
|
|
122
130
|
},
|
|
123
131
|
})
|
|
132
|
+
|
|
133
|
+
// Custom endpoints
|
|
134
|
+
type MyEndpoints = {
|
|
135
|
+
'/api/my-endpoint': {
|
|
136
|
+
method: 'GET'
|
|
137
|
+
request: never
|
|
138
|
+
response: string
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// or
|
|
143
|
+
type ExtendedEndpoints = BaseApiEndpoints & {
|
|
144
|
+
'/api/my-endpoint': {
|
|
145
|
+
method: 'GET'
|
|
146
|
+
request: never
|
|
147
|
+
response: string
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// In case of usage of custom Endpoints, we have to specify the URL as generic parameter
|
|
152
|
+
const myResponse = await client.makeRequest<MyEndpoints, '/api/my-endpoint'>(
|
|
153
|
+
'/api/my-endpoint'
|
|
154
|
+
)
|
|
124
155
|
```
|
|
125
156
|
|
|
126
|
-
##### `makeStreamRequest<
|
|
157
|
+
##### `makeStreamRequest<Endpoints, URL>(endpoint, data, callbacks, signal?): () => void`
|
|
127
158
|
|
|
128
|
-
Make a type-safe streaming request to
|
|
159
|
+
Make a type-safe streaming request to any streaming endpoints. `endpoint` must be one of the key of `Endpoints` type
|
|
160
|
+
that define schema of the available endpoints. All default Ragbits routes are supported out of the box.
|
|
129
161
|
|
|
130
162
|
**Parameters:**
|
|
131
163
|
|
|
132
|
-
- `endpoint`:
|
|
164
|
+
- `endpoint`: Streaming endpoint path (e.g., '/api/chat')
|
|
133
165
|
- `data`: Request data (typed based on endpoint)
|
|
134
166
|
- `callbacks`: Stream callbacks
|
|
135
167
|
- `onMessage`: Called when a message chunk is received
|
|
@@ -147,8 +179,8 @@ const cleanup = client.makeStreamRequest(
|
|
|
147
179
|
{
|
|
148
180
|
message: 'Tell me about AI',
|
|
149
181
|
history: [
|
|
150
|
-
{ role: 'user', content: 'Hello'
|
|
151
|
-
{ role: 'assistant', content: 'Hi there!'
|
|
182
|
+
{ role: 'user', content: 'Hello' },
|
|
183
|
+
{ role: 'assistant', content: 'Hi there!' },
|
|
152
184
|
],
|
|
153
185
|
context: { user_id: 'user-123' },
|
|
154
186
|
},
|
|
@@ -177,105 +209,23 @@ const cleanup = client.makeStreamRequest(
|
|
|
177
209
|
|
|
178
210
|
// Cancel stream
|
|
179
211
|
cleanup()
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Types
|
|
183
|
-
|
|
184
|
-
### Core Types
|
|
185
|
-
|
|
186
|
-
#### `ClientConfig`
|
|
187
212
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
#### `Message`
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
interface Message {
|
|
198
|
-
role: MessageRole
|
|
199
|
-
content: string
|
|
200
|
-
id?: string
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
enum MessageRole {
|
|
204
|
-
USER = 'user',
|
|
205
|
-
ASSISTANT = 'assistant',
|
|
206
|
-
SYSTEM = 'system',
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
#### `ChatRequest`
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
interface ChatRequest {
|
|
214
|
-
message: string
|
|
215
|
-
history: Message[]
|
|
216
|
-
context?: Record<string, unknown>
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
#### `TypedChatResponse`
|
|
221
|
-
|
|
222
|
-
Union type for all possible chat response types:
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
type TypedChatResponse =
|
|
226
|
-
| { type: 'text'; content: string }
|
|
227
|
-
| { type: 'reference'; content: Reference }
|
|
228
|
-
| { type: 'message_id'; content: string }
|
|
229
|
-
| { type: 'conversation_id'; content: string }
|
|
230
|
-
| { type: 'state_update'; content: ServerState }
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
#### `FeedbackRequest`
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
interface FeedbackRequest {
|
|
237
|
-
message_id: string
|
|
238
|
-
feedback: FeedbackType
|
|
239
|
-
payload: Record<string, unknown> | null
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
enum FeedbackType {
|
|
243
|
-
LIKE = 'like',
|
|
244
|
-
DISLIKE = 'dislike',
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
#### `ConfigResponse`
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
interface ConfigResponse {
|
|
252
|
-
feedback: {
|
|
253
|
-
like: { enabled: boolean; form: RJSFSchema | null }
|
|
254
|
-
dislike: { enabled: boolean; form: RJSFSchema | null }
|
|
213
|
+
// Custom endpoints
|
|
214
|
+
type MyEndpoints = {
|
|
215
|
+
'/api/my-endpoint': {
|
|
216
|
+
method: 'GET'
|
|
217
|
+
request: never
|
|
218
|
+
response: string
|
|
255
219
|
}
|
|
256
|
-
customization: UICustomization | null
|
|
257
220
|
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
#### `StreamCallbacks<T, E>`
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
222
|
+
// In case of usage of custom Endpoints, we have to specify the URL as generic parameter
|
|
223
|
+
const cleanup = client.makeStreamRequest<MyEndpoints, '/api/my-endpoint'>(
|
|
224
|
+
'/api/my-endpoint',
|
|
225
|
+
...
|
|
226
|
+
)
|
|
268
227
|
```
|
|
269
228
|
|
|
270
|
-
### Advanced Types
|
|
271
|
-
|
|
272
|
-
The package provides extensive TypeScript support with predefined endpoint types:
|
|
273
|
-
|
|
274
|
-
- `ApiEndpointPath` - Union of all available API endpoints
|
|
275
|
-
- `StreamingEndpointPath` - Union of all available streaming endpoints
|
|
276
|
-
- `ApiEndpointResponse<T>` - Response type for a specific endpoint
|
|
277
|
-
- `StreamingEndpointStream<T>` - Stream response type for a specific endpoint
|
|
278
|
-
|
|
279
229
|
## Error Handling
|
|
280
230
|
|
|
281
231
|
The client provides comprehensive error handling:
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { http, HttpResponse } from 'msw'
|
|
2
|
+
import { defaultConfigResponse } from '../utils'
|
|
3
|
+
|
|
4
|
+
export const handlers = [
|
|
5
|
+
// Config endpoint with conditional error handling
|
|
6
|
+
http.get('http://127.0.0.1:8000/api/config', ({ request }) => {
|
|
7
|
+
const url = new URL(request.url)
|
|
8
|
+
if (url.searchParams.get('error') === 'true') {
|
|
9
|
+
return new HttpResponse(null, { status: 500 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return HttpResponse.json(defaultConfigResponse)
|
|
13
|
+
}),
|
|
14
|
+
|
|
15
|
+
// Feedback endpoint
|
|
16
|
+
http.post('http://127.0.0.1:8000/api/feedback', async ({ request }) => {
|
|
17
|
+
const _body = await request.json()
|
|
18
|
+
return HttpResponse.json({
|
|
19
|
+
status: 'success',
|
|
20
|
+
})
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
// Chat streaming endpoint
|
|
24
|
+
http.post('http://127.0.0.1:8000/api/chat', ({ request }) => {
|
|
25
|
+
const url = new URL(request.url)
|
|
26
|
+
|
|
27
|
+
// Handle different test scenarios
|
|
28
|
+
if (url.searchParams.get('error') === 'true') {
|
|
29
|
+
return new HttpResponse(null, { status: 500 })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (url.searchParams.get('malformed') === 'true') {
|
|
33
|
+
const encoder = new TextEncoder()
|
|
34
|
+
const stream = new ReadableStream({
|
|
35
|
+
start(controller) {
|
|
36
|
+
controller.enqueue(encoder.encode('data: invalid-json\n\n'))
|
|
37
|
+
controller.close()
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
return new HttpResponse(stream, {
|
|
41
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (url.searchParams.get('empty') === 'true') {
|
|
46
|
+
return new HttpResponse(null, {
|
|
47
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (url.searchParams.get('slow') === 'true') {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
resolve(
|
|
55
|
+
HttpResponse.json({
|
|
56
|
+
type: 'text',
|
|
57
|
+
content: 'Slow response',
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
}, 1000)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const encoder = new TextEncoder()
|
|
65
|
+
|
|
66
|
+
const stream = new ReadableStream({
|
|
67
|
+
start(controller) {
|
|
68
|
+
const messages = [
|
|
69
|
+
{ type: 'text', content: 'Hello there!' },
|
|
70
|
+
{ type: 'text', content: 'How can I help you?' },
|
|
71
|
+
{ type: 'message_id', content: 'msg-123' },
|
|
72
|
+
{ type: 'conversation_id', content: 'conv-456' },
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
messages.forEach((message, index) => {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
controller.enqueue(
|
|
78
|
+
encoder.encode(
|
|
79
|
+
`data: ${JSON.stringify(message)}\n\n`
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
if (index === messages.length - 1) {
|
|
83
|
+
controller.close()
|
|
84
|
+
}
|
|
85
|
+
}, index * 10)
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return new HttpResponse(stream, {
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'text/event-stream',
|
|
93
|
+
'Cache-Control': 'no-cache',
|
|
94
|
+
Connection: 'keep-alive',
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
}),
|
|
98
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { beforeAll, afterEach, afterAll } from 'vitest'
|
|
3
|
+
import { setupServer } from 'msw/node'
|
|
4
|
+
import { handlers } from './mocks/handlers'
|
|
5
|
+
|
|
6
|
+
// Setup MSW server
|
|
7
|
+
export const server = setupServer(...handlers)
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
// Start the interception on the client side before all tests run.
|
|
11
|
+
server.listen({ onUnhandledRequest: 'error' })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
// Reset any request handlers that we may add during the tests,
|
|
16
|
+
// so they don't affect other tests.
|
|
17
|
+
server.resetHandlers()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
// Clean up after the tests are finished.
|
|
22
|
+
server.close()
|
|
23
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ConfigResponse } from '../src'
|
|
2
|
+
|
|
3
|
+
// Shared config response for tests
|
|
4
|
+
export const defaultConfigResponse: ConfigResponse = {
|
|
5
|
+
feedback: {
|
|
6
|
+
like: {
|
|
7
|
+
enabled: true,
|
|
8
|
+
form: {
|
|
9
|
+
title: 'Like Form',
|
|
10
|
+
type: 'object',
|
|
11
|
+
required: ['like_reason'],
|
|
12
|
+
properties: {
|
|
13
|
+
like_reason: {
|
|
14
|
+
title: 'Like Reason',
|
|
15
|
+
description: 'Why do you like this?',
|
|
16
|
+
type: 'string',
|
|
17
|
+
minLength: 1,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
dislike: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
form: null,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
customization: null,
|
|
28
|
+
user_settings: {
|
|
29
|
+
form: {
|
|
30
|
+
title: 'Chat Form',
|
|
31
|
+
type: 'object',
|
|
32
|
+
required: ['language'],
|
|
33
|
+
properties: {
|
|
34
|
+
language: {
|
|
35
|
+
title: 'Language',
|
|
36
|
+
description: 'Please select the language',
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['English', 'Polish'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
debug_mode: false,
|
|
44
|
+
conversation_history: false,
|
|
45
|
+
}
|