@ragbits/api-client 0.0.1

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,146 @@
1
+ # Ragbits API Client
2
+
3
+ A TypeScript client for communicating with the Ragbits API. This client provides methods for both regular HTTP requests and server-sent events (streaming) functionality.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install ragbits-api-client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { RagbitsClient, type ChatResponse } from 'ragbits-api-client'
15
+
16
+ // Initialize the client
17
+ const client = new RagbitsClient({
18
+ baseUrl: 'http://127.0.0.1:8000', // Optional, defaults to http://127.0.0.1:8000
19
+ })
20
+
21
+ // Get API configuration
22
+ const config = await client.getConfig()
23
+
24
+ // Send a chat message with streaming response
25
+ const cleanup = client.sendChatMessage(
26
+ {
27
+ message: 'Hello!',
28
+ history: [],
29
+ context: {}, // Optional
30
+ },
31
+ {
32
+ onMessage: (data: ChatResponse) => {
33
+ console.log('Received message:', data)
34
+ },
35
+ onError: (error: string) => {
36
+ console.error('Error:', error)
37
+ },
38
+ onClose: () => {
39
+ console.log('Stream closed')
40
+ },
41
+ }
42
+ )
43
+
44
+ // Cancel the stream if needed
45
+ cleanup()
46
+
47
+ // Send feedback
48
+ await client.sendFeedback({
49
+ message_id: 'message-123',
50
+ feedback: 'like',
51
+ payload: { reason: 'helpful' },
52
+ })
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### `RagbitsClient`
58
+
59
+ The main client class for interacting with the Ragbits API.
60
+
61
+ #### Constructor
62
+
63
+ ```typescript
64
+ new RagbitsClient(config?: { baseUrl?: string })
65
+ ```
66
+
67
+ - `config.baseUrl` (optional): Base URL for the API. Defaults to 'http://127.0.0.1:8000'
68
+
69
+ #### Methods
70
+
71
+ ##### `getConfig()`
72
+
73
+ Get the API configuration.
74
+
75
+ - Returns: `Promise<Record<string, unknown>>` - API configuration object
76
+
77
+ ##### `sendChatMessage(chatRequest, callbacks)`
78
+
79
+ Send a chat message and receive streaming responses.
80
+
81
+ - Parameters:
82
+ - `chatRequest`: ChatRequest
83
+ - `message`: string - User message
84
+ - `history`: Array<{ role: string; content: string; id?: string }> - Chat history
85
+ - `context`: Record<string, unknown> (optional) - Additional context
86
+ - `callbacks`: StreamCallbacks
87
+ - `onMessage`: (data: ChatResponse) => void | Promise<void> - Called when a message chunk is received
88
+ - `onError`: (error: string) => void | Promise<void> - Called when an error occurs
89
+ - `onClose`: () => void | Promise<void> (optional) - Called when the stream closes
90
+ - Returns: `() => void` - Cleanup function to cancel the stream
91
+
92
+ ##### `sendFeedback(feedbackData)`
93
+
94
+ Send feedback for a message.
95
+
96
+ - Parameters:
97
+ - `feedbackData`: FeedbackRequest
98
+ - `message_id`: string - ID of the message to provide feedback for
99
+ - `feedback`: string - Type of feedback
100
+ - `payload`: Record<string, unknown> | null - Additional feedback data
101
+ - Returns: `Promise<Record<string, unknown>>` - Feedback submission response
102
+
103
+ ## Types
104
+
105
+ ### ChatResponse
106
+
107
+ ```typescript
108
+ {
109
+ type: 'message' | 'reference' | 'state_update' | 'text' | 'message_id'
110
+ content: any
111
+ }
112
+ ```
113
+
114
+ ### ChatRequest
115
+
116
+ ```typescript
117
+ {
118
+ message: string;
119
+ history: Array<{
120
+ role: string;
121
+ content: string;
122
+ id?: string;
123
+ }>;
124
+ context?: Record<string, unknown>;
125
+ }
126
+ ```
127
+
128
+ ### FeedbackRequest
129
+
130
+ ```typescript
131
+ {
132
+ message_id: string
133
+ feedback: string
134
+ payload: Record<string, unknown> | null
135
+ }
136
+ ```
137
+
138
+ ### StreamCallbacks
139
+
140
+ ```typescript
141
+ {
142
+ onMessage: (data: ChatResponse) => void | Promise<void>;
143
+ onError: (error: string) => void | Promise<void>;
144
+ onClose?: () => void | Promise<void>;
145
+ }
146
+ ```
@@ -0,0 +1,21 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import tseslint from 'typescript-eslint'
4
+
5
+ export default tseslint.config(
6
+ { ignores: ['dist', 'coverage'] },
7
+ {
8
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
9
+ files: ['**/*.{ts,tsx}'],
10
+ languageOptions: {
11
+ ecmaVersion: 2020,
12
+ globals: globals.node,
13
+ },
14
+ rules: {
15
+ '@typescript-eslint/no-unused-vars': [
16
+ 'error',
17
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
18
+ ],
19
+ },
20
+ }
21
+ )
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@ragbits/api-client",
3
+ "version": "0.0.1",
4
+ "description": "JavaScript client for the Ragbits API",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format cjs,esm --dts",
18
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
19
+ "test": "vitest",
20
+ "test:run": "vitest run",
21
+ "test:coverage": "vitest run --coverage",
22
+ "lint": "eslint .",
23
+ "format": "prettier --write .",
24
+ "format:check": "prettier --check ."
25
+ },
26
+ "keywords": [
27
+ "ragbits",
28
+ "api",
29
+ "client"
30
+ ],
31
+ "author": "deepsense.ai",
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "@eslint/js": "^9.17.0",
35
+ "@rjsf/utils": "^5.24.12",
36
+ "@testing-library/jest-dom": "^6.4.0",
37
+ "@types/node": "^20.0.0",
38
+ "@vitest/coverage-v8": "^1.6.0",
39
+ "eslint": "^9.17.0",
40
+ "globals": "^15.14.0",
41
+ "msw": "^2.0.0",
42
+ "prettier": "^3.4.2",
43
+ "tsup": "^8.0.0",
44
+ "typescript": "^5.0.0",
45
+ "typescript-eslint": "^8.18.2",
46
+ "vitest": "^1.6.0"
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ trailingComma: 'es5',
3
+ tabWidth: 4,
4
+ semi: false,
5
+ singleQuote: true,
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,213 @@
1
+ import type {
2
+ ClientConfig,
3
+ StreamCallbacks,
4
+ ApiEndpointPath,
5
+ ApiEndpointResponse,
6
+ TypedApiRequestOptions,
7
+ StreamingEndpointPath,
8
+ StreamingEndpointRequest,
9
+ StreamingEndpointStream,
10
+ } from './types'
11
+
12
+ /**
13
+ * Client for communicating with the Ragbits API
14
+ */
15
+ export class RagbitsClient {
16
+ private readonly baseUrl: string
17
+
18
+ /**
19
+ * @param config - Configuration object
20
+ */
21
+ constructor(config: ClientConfig = {}) {
22
+ this.baseUrl = config.baseUrl || 'http://127.0.0.1:8000'
23
+
24
+ // Validate the base URL
25
+ try {
26
+ new URL(this.baseUrl)
27
+ } catch {
28
+ throw new Error(
29
+ `Invalid base URL: ${this.baseUrl}. Please provide a valid URL.`
30
+ )
31
+ }
32
+
33
+ if (this.baseUrl.endsWith('/')) {
34
+ this.baseUrl = this.baseUrl.slice(0, -1)
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the base URL used by this client
40
+ */
41
+ getBaseUrl(): string {
42
+ return this.baseUrl
43
+ }
44
+
45
+ /**
46
+ * Build full API URL from path
47
+ * @private
48
+ */
49
+ private _buildApiUrl(path: string): string {
50
+ return `${this.baseUrl}${path}`
51
+ }
52
+
53
+ /**
54
+ * Make a request to the API
55
+ * @private
56
+ */
57
+ private async _makeRequest(
58
+ url: string,
59
+ options: RequestInit = {}
60
+ ): Promise<Response> {
61
+ const defaultOptions: RequestInit = {
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ }
66
+
67
+ const response = await fetch(url, { ...defaultOptions, ...options })
68
+ if (!response.ok) {
69
+ throw new Error(`HTTP error! status: ${response.status}`)
70
+ }
71
+ return response
72
+ }
73
+
74
+ /**
75
+ * Method to make API requests to known endpoints only
76
+ * @param endpoint - API endpoint path (must be predefined)
77
+ * @param options - Typed request options for the specific endpoint
78
+ */
79
+ async makeRequest<T extends ApiEndpointPath>(
80
+ endpoint: T,
81
+ options?: TypedApiRequestOptions<T>
82
+ ): Promise<ApiEndpointResponse<T>> {
83
+ const {
84
+ method = 'GET',
85
+ body,
86
+ headers = {},
87
+ ...restOptions
88
+ } = options || {}
89
+
90
+ const requestOptions: RequestInit = {
91
+ method,
92
+ headers,
93
+ ...restOptions, // This will include signal and other fetch options
94
+ }
95
+
96
+ if (body && method !== 'GET') {
97
+ requestOptions.body =
98
+ typeof body === 'string' ? body : JSON.stringify(body)
99
+ }
100
+
101
+ const response = await this._makeRequest(
102
+ this._buildApiUrl(endpoint),
103
+ requestOptions
104
+ )
105
+ return response.json()
106
+ }
107
+
108
+ /**
109
+ * Method for streaming requests to known endpoints only
110
+ * @param endpoint - Streaming endpoint path (must be predefined)
111
+ * @param data - Request data
112
+ * @param callbacks - Stream callbacks
113
+ * @param signal - Optional AbortSignal for cancelling the request
114
+ */
115
+ makeStreamRequest<T extends StreamingEndpointPath>(
116
+ endpoint: T,
117
+ data: StreamingEndpointRequest<T>,
118
+ callbacks: StreamCallbacks<StreamingEndpointStream<T>>,
119
+ signal?: AbortSignal
120
+ ): () => void {
121
+ let isCancelled = false
122
+
123
+ const processStream = async (response: Response): Promise<void> => {
124
+ const reader = response.body
125
+ ?.pipeThrough(new TextDecoderStream())
126
+ .getReader()
127
+
128
+ if (!reader) {
129
+ throw new Error('Response body is null')
130
+ }
131
+
132
+ while (!isCancelled && !signal?.aborted) {
133
+ try {
134
+ const { value, done } = await reader.read()
135
+ if (done) {
136
+ callbacks.onClose?.()
137
+ break
138
+ }
139
+
140
+ const lines = value.split('\n')
141
+ for (const line of lines) {
142
+ if (!line.startsWith('data: ')) continue
143
+
144
+ try {
145
+ const jsonString = line.replace('data: ', '').trim()
146
+ const parsedData = JSON.parse(
147
+ jsonString
148
+ ) as StreamingEndpointStream<T>
149
+ await callbacks.onMessage(parsedData)
150
+ } catch (parseError) {
151
+ console.error('Error parsing JSON:', parseError)
152
+ await callbacks.onError(
153
+ new Error('Error processing server response')
154
+ )
155
+ }
156
+ }
157
+ } catch (streamError) {
158
+ console.error('Stream error:', streamError)
159
+ await callbacks.onError(new Error('Error reading stream'))
160
+ break
161
+ }
162
+ }
163
+ }
164
+
165
+ const startStream = async (): Promise<void> => {
166
+ try {
167
+ const response = await fetch(this._buildApiUrl(endpoint), {
168
+ method: 'POST',
169
+ headers: {
170
+ 'Content-Type': 'application/json',
171
+ Accept: 'text/event-stream',
172
+ },
173
+ body: JSON.stringify(data),
174
+ signal,
175
+ })
176
+
177
+ if (!response.ok) {
178
+ throw new Error(`HTTP error! status: ${response.status}`)
179
+ }
180
+
181
+ await processStream(response)
182
+ } catch (error) {
183
+ if (signal?.aborted) {
184
+ return
185
+ }
186
+
187
+ console.error('Request error:', error)
188
+ const errorMessage =
189
+ error instanceof Error
190
+ ? error.message
191
+ : 'Error connecting to server'
192
+ await callbacks.onError(new Error(errorMessage))
193
+ }
194
+ }
195
+
196
+ try {
197
+ startStream()
198
+ } catch (error) {
199
+ const errorMessage =
200
+ error instanceof Error
201
+ ? error.message
202
+ : 'Failed to start stream'
203
+ callbacks.onError(new Error(errorMessage))
204
+ }
205
+
206
+ return () => {
207
+ isCancelled = true
208
+ }
209
+ }
210
+ }
211
+
212
+ // Re-export types
213
+ export * from './types'