@aui.io/aui-client-staging 0.0.1 → 0.0.3
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 +778 -137
- package/dist/cjs/Client.js +2 -2
- package/dist/cjs/api/resources/apolloWsSession/client/Client.js +3 -1
- package/dist/cjs/api/resources/apolloWsSession/client/index.d.ts +1 -1
- package/dist/cjs/api/resources/apolloWsSession/client/index.js +3 -0
- package/dist/cjs/core/websocket/ws.js +29 -1
- package/dist/cjs/environments.d.ts +8 -7
- package/dist/cjs/environments.js +8 -4
- package/dist/cjs/exports.d.ts +7 -1
- package/dist/cjs/exports.js +11 -15
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Client.mjs +2 -2
- package/dist/esm/api/resources/apolloWsSession/client/Client.mjs +3 -1
- package/dist/esm/api/resources/apolloWsSession/client/index.d.mts +1 -1
- package/dist/esm/api/resources/apolloWsSession/client/index.mjs +1 -1
- package/dist/esm/core/websocket/ws.mjs +29 -1
- package/dist/esm/environments.d.mts +8 -7
- package/dist/esm/environments.mjs +8 -4
- package/dist/esm/exports.d.mts +7 -1
- package/dist/esm/exports.mjs +9 -1
- package/dist/esm/version.d.mts +1 -1
- package/dist/esm/version.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,244 +1,885 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aui.io/aui-client-staging-staging
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@aui.io/aui-client-staging-staging)
|
|
4
|
+
[](https://buildwithfern.com)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
> **Official TypeScript/JavaScript SDK for AUI APIs (STAGING)** - Provides REST and WebSocket support for intelligent agent communication against staging environments.
|
|
7
7
|
|
|
8
|
-
## Installation
|
|
8
|
+
## 🚀 Installation
|
|
9
9
|
|
|
10
|
-
```
|
|
11
|
-
npm
|
|
10
|
+
```bash
|
|
11
|
+
npm install @aui.io/aui-client-staging-staging
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## ⚡ Quick Start
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
16
|
+
```typescript
|
|
17
|
+
import { ApolloClient, ApolloEnvironment } from '@aui.io/aui-client-staging';
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
// Default: Uses Gcp environment
|
|
20
|
+
const client = new ApolloClient({
|
|
21
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
22
|
+
});
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
// Or explicitly choose an environment:
|
|
25
|
+
const gcpClient = new ApolloClient({
|
|
26
|
+
environment: ApolloEnvironment.Gcp,
|
|
27
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
28
|
+
});
|
|
24
29
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
task_origin_type: "stores"
|
|
30
|
+
const azureClient = new ApolloClient({
|
|
31
|
+
environment: ApolloEnvironment.Azure,
|
|
32
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
29
33
|
});
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
## Request And Response Types
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
following namespace:
|
|
37
|
+
### REST API - Create and Manage Tasks
|
|
36
38
|
|
|
37
39
|
```typescript
|
|
38
|
-
|
|
40
|
+
// Create a new task
|
|
41
|
+
const taskResponse = await client.controllerApi.createTask({
|
|
42
|
+
user_id: 'user123',
|
|
43
|
+
task_origin_type: 'web-widget' // Required: identifies the source of the task
|
|
44
|
+
});
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
console.log('Task ID:', taskResponse.id);
|
|
47
|
+
console.log('Welcome:', taskResponse.welcome_message);
|
|
48
|
+
|
|
49
|
+
// Get all messages for a task
|
|
50
|
+
const messages = await client.controllerApi.getTaskMessages(taskResponse.id);
|
|
51
|
+
console.log('Messages:', messages);
|
|
52
|
+
|
|
53
|
+
// Submit a message to an existing task
|
|
54
|
+
const messageResponse = await client.controllerApi.sendMessage({
|
|
55
|
+
task_id: taskResponse.id,
|
|
56
|
+
text: 'Looking for a microwave with at least 20 liters capacity',
|
|
57
|
+
is_external_api: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log('Agent response:', messageResponse.text);
|
|
61
|
+
|
|
62
|
+
// Get all tasks for a user
|
|
63
|
+
const userTasks = await client.controllerApi.listUserTasks({
|
|
64
|
+
user_id: 'user123',
|
|
65
|
+
page: 1,
|
|
66
|
+
size: 10
|
|
67
|
+
});
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
console.log('Total tasks:', userTasks.total);
|
|
70
|
+
```
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
will be thrown.
|
|
72
|
+
### WebSocket - Real-time Agent Communication
|
|
49
73
|
|
|
50
74
|
```typescript
|
|
51
|
-
|
|
75
|
+
// Connect to WebSocket with authentication headers
|
|
76
|
+
const socket = await client.apolloWsSession.connect({
|
|
77
|
+
debug: false,
|
|
78
|
+
reconnectAttempts: 3,
|
|
79
|
+
headers: {
|
|
80
|
+
'x-network-api-key': 'API_KEY_YOUR_KEY_HERE'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
52
83
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
// Listen for connection open
|
|
85
|
+
socket.on('open', () => {
|
|
86
|
+
console.log('✅ Connected to agent');
|
|
87
|
+
|
|
88
|
+
// Send a message
|
|
89
|
+
socket.sendUserMessage({
|
|
90
|
+
task_id: 'your-task-id',
|
|
91
|
+
text: 'I need product recommendations for gaming laptops'
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Handle streaming responses
|
|
96
|
+
socket.on('message', (message) => {
|
|
97
|
+
// Streaming updates (partial responses)
|
|
98
|
+
if (message.channel?.eventName === 'thread-message-text-content-updated') {
|
|
99
|
+
console.log('Agent is typing:', message.data?.text);
|
|
61
100
|
}
|
|
101
|
+
|
|
102
|
+
// Final message with complete response
|
|
103
|
+
if (message.id && message.text && message.sender) {
|
|
104
|
+
console.log('Complete response:', message.text);
|
|
105
|
+
|
|
106
|
+
// Handle product recommendations (if any)
|
|
107
|
+
if (message.cards && message.cards.length > 0) {
|
|
108
|
+
message.cards.forEach(card => {
|
|
109
|
+
console.log(`${card.name} - ${card.id}`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Follow-up suggestions
|
|
114
|
+
if (message.followupSuggestions) {
|
|
115
|
+
console.log('Suggestions:', message.followupSuggestions);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Error messages
|
|
120
|
+
if (message.statusCode) {
|
|
121
|
+
console.error('Agent error:', message.description);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Handle errors
|
|
126
|
+
socket.on('error', (error) => {
|
|
127
|
+
console.error('WebSocket error:', error);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Handle connection close
|
|
131
|
+
socket.on('close', (event) => {
|
|
132
|
+
console.log('Connection closed:', event.code);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Close connection when done
|
|
136
|
+
// socket.close();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 📖 API Reference
|
|
140
|
+
|
|
141
|
+
### Client Configuration
|
|
142
|
+
|
|
143
|
+
The `ApolloClient` constructor accepts the following options:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface ApolloClient.Options {
|
|
147
|
+
environment?: ApolloEnvironment; // Use predefined environment (Gcp or Azure)
|
|
148
|
+
|
|
149
|
+
// Authentication (required)
|
|
150
|
+
networkApiKey: string; // Your API key (x-network-api-key header)
|
|
151
|
+
|
|
152
|
+
// Optional configurations
|
|
153
|
+
headers?: Record<string, string>; // Additional headers
|
|
154
|
+
timeoutInSeconds?: number; // Request timeout (default: 60)
|
|
155
|
+
maxRetries?: number; // Max retry attempts (default: 2)
|
|
156
|
+
fetch?: typeof fetch; // Custom fetch implementation
|
|
62
157
|
}
|
|
63
158
|
```
|
|
64
159
|
|
|
65
|
-
|
|
160
|
+
### Available Environments
|
|
66
161
|
|
|
67
|
-
|
|
162
|
+
The SDK supports multiple environments for both REST API and WebSocket connections:
|
|
68
163
|
|
|
69
|
-
|
|
164
|
+
```typescript
|
|
165
|
+
import { ApolloEnvironment } from '@aui.io/aui-client-staging';
|
|
166
|
+
|
|
167
|
+
// Gcp Environment (Default)
|
|
168
|
+
ApolloEnvironment.Gcp = {
|
|
169
|
+
base: "https://api-staging.internal-aui.io/ia-controller", // REST API
|
|
170
|
+
wsUrl: "wss://api-staging.internal-aui.io" // WebSocket
|
|
171
|
+
}
|
|
70
172
|
|
|
173
|
+
// Azure Environment
|
|
174
|
+
ApolloEnvironment.Azure = {
|
|
175
|
+
base: "https://azure-staging-v2.aui.io/ia-controller", // REST API
|
|
176
|
+
wsUrl: "wss://azure-staging-v2.aui.io" // WebSocket
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Default (same as Gcp)
|
|
180
|
+
ApolloEnvironment.Default = {
|
|
181
|
+
base: "https://api-staging.internal-aui.io/ia-controller",
|
|
182
|
+
wsUrl: "wss://api-staging.internal-aui.io"
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Usage Example:**
|
|
71
187
|
```typescript
|
|
72
|
-
|
|
188
|
+
import { ApolloClient, ApolloEnvironment } from '@aui.io/aui-client-staging';
|
|
189
|
+
|
|
190
|
+
// Use Azure environment
|
|
191
|
+
const client = new ApolloClient({
|
|
192
|
+
environment: ApolloEnvironment.Azure,
|
|
193
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Both REST and WebSocket will use Azure endpoints
|
|
197
|
+
const task = await client.controllerApi.createTask({...});
|
|
198
|
+
const socket = await client.apolloWsSession.connect({
|
|
199
|
+
debug: false,
|
|
200
|
+
reconnectAttempts: 3,
|
|
73
201
|
headers: {
|
|
74
|
-
'
|
|
202
|
+
'x-network-api-key': 'API_KEY_YOUR_KEY_HERE'
|
|
75
203
|
}
|
|
76
204
|
});
|
|
77
205
|
```
|
|
78
206
|
|
|
79
|
-
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### REST API Methods
|
|
210
|
+
|
|
211
|
+
All methods are accessed via `client.controllerApi.*`
|
|
212
|
+
|
|
213
|
+
#### `createTask(request)` - Create Task
|
|
214
|
+
Create a new task for the agent.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const taskResponse = await client.controllerApi.createTask({
|
|
218
|
+
user_id: string, // Unique user identifier
|
|
219
|
+
task_origin_type: string // Required: origin type (e.g., 'web-widget', 'mobile-app', 'api')
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Returns: { id: string, user_id: string, title: string, welcome_message?: string }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Note:** `task_origin_type` is required in v1.2.17+. Common values: `'web-widget'`, `'mobile-app'`, `'api'`, `'internal-tool'`
|
|
226
|
+
|
|
227
|
+
#### `getTaskMessages(taskId)` - Get Task Messages
|
|
228
|
+
Retrieve all messages for a specific task.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
const messages = await client.controllerApi.getTaskMessages(taskId: string);
|
|
232
|
+
|
|
233
|
+
// Returns: Message[] - Array of messages
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### `sendMessage(request)` - Send Message
|
|
237
|
+
Submit a new message to an existing task (non-streaming).
|
|
80
238
|
|
|
81
|
-
|
|
239
|
+
```typescript
|
|
240
|
+
const messageResponse = await client.controllerApi.sendMessage({
|
|
241
|
+
task_id: string, // Task identifier
|
|
242
|
+
text: string, // Message text
|
|
243
|
+
is_external_api?: boolean, // Optional: mark as external API call
|
|
244
|
+
context?: { // Optional: additional context
|
|
245
|
+
url?: string,
|
|
246
|
+
lead_details?: Record<string, any>,
|
|
247
|
+
welcome_message?: string
|
|
248
|
+
},
|
|
249
|
+
agent_variables?: Record<string, unknown> // Optional: custom agent variables (NEW in v1.2.28)
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Returns: Message - Complete agent response with optional product cards
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**New in v1.2.28:** The `agent_variables` parameter allows you to pass custom context to the agent:
|
|
82
256
|
|
|
83
257
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
258
|
+
// Example: Send message with agent variables
|
|
259
|
+
const response = await client.controllerApi.sendMessage({
|
|
260
|
+
task_id: 'your-task-id',
|
|
261
|
+
text: 'What products do you recommend?',
|
|
262
|
+
is_external_api: true,
|
|
263
|
+
agent_variables: {
|
|
264
|
+
context: 'User is interested in electric vehicles',
|
|
265
|
+
user_preference: 'eco-friendly',
|
|
266
|
+
budget: 'mid-range'
|
|
87
267
|
}
|
|
88
268
|
});
|
|
89
269
|
```
|
|
90
270
|
|
|
91
|
-
|
|
271
|
+
#### `listUserTasks(request)` - List User Tasks
|
|
272
|
+
Retrieve all tasks for a specific user with pagination.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const tasksResponse = await client.controllerApi.listUserTasks({
|
|
276
|
+
user_id: string, // User identifier
|
|
277
|
+
page?: number, // Page number (optional, default: 1)
|
|
278
|
+
size?: number // Page size (optional, default: 10)
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Returns: { tasks: Task[], total: number, page: number, size: number }
|
|
282
|
+
```
|
|
92
283
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
retry limit (default: 2).
|
|
284
|
+
#### `getProductMetadata(link)` - Get Product Metadata
|
|
285
|
+
Retrieve metadata for a product from a given URL/link.
|
|
96
286
|
|
|
97
|
-
|
|
287
|
+
```typescript
|
|
288
|
+
const metadata = await client.controllerApi.getProductMetadata({
|
|
289
|
+
link: string // Product URL or link
|
|
290
|
+
});
|
|
98
291
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
|
|
292
|
+
// Returns: Record<string, any> - Product metadata object
|
|
293
|
+
```
|
|
102
294
|
|
|
103
|
-
|
|
295
|
+
#### `getAgentContext(request)` - Get Agent Context (NEW in v1.2.28)
|
|
296
|
+
Retrieve the agent's context configuration including parameters, entities, and static context.
|
|
104
297
|
|
|
105
298
|
```typescript
|
|
106
|
-
const
|
|
107
|
-
|
|
299
|
+
const agentContext = await client.controllerApi.getAgentContext({
|
|
300
|
+
task_id: 'your-task-id',
|
|
301
|
+
query: 'test context'
|
|
108
302
|
});
|
|
303
|
+
|
|
304
|
+
// Returns: CreateTopicRequestBody - Agent context with:
|
|
305
|
+
// - title: string
|
|
306
|
+
// - params: TaskParameter[]
|
|
307
|
+
// - entities: TaskTopicEntity[]
|
|
308
|
+
// - static_context: string
|
|
109
309
|
```
|
|
110
310
|
|
|
111
|
-
|
|
311
|
+
**Example:**
|
|
112
312
|
|
|
113
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
// Get agent context to understand available parameters
|
|
315
|
+
const context = await client.controllerApi.getAgentContext({
|
|
316
|
+
task_id: 'your-task-id',
|
|
317
|
+
query: 'test context'
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
console.log('Agent Title:', context.title);
|
|
321
|
+
console.log('Available Parameters:', context.params?.length);
|
|
322
|
+
console.log('Entities:', context.entities?.length);
|
|
323
|
+
console.log('Static Context:', context.static_context);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `getDirectFollowupSuggestions(taskId)` - Get Direct Followup Suggestions (NEW in v1.2.28)
|
|
327
|
+
Retrieve AI-generated followup suggestions for a specific task.
|
|
114
328
|
|
|
115
329
|
```typescript
|
|
116
|
-
const
|
|
117
|
-
|
|
330
|
+
const suggestions: string[] = await client.controllerApi.getDirectFollowupSuggestions('your-task-id');
|
|
331
|
+
|
|
332
|
+
// Returns: string[] - Array of suggested followup questions
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Example:**
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// Get followup suggestions for a task
|
|
339
|
+
const suggestions: string[] = await client.controllerApi.getDirectFollowupSuggestions('your-task-id');
|
|
340
|
+
|
|
341
|
+
console.log('Suggested followups:');
|
|
342
|
+
suggestions.forEach((suggestion, index) => {
|
|
343
|
+
console.log(`${index + 1}. ${suggestion}`);
|
|
118
344
|
});
|
|
119
345
|
```
|
|
120
346
|
|
|
121
|
-
|
|
347
|
+
---
|
|
122
348
|
|
|
123
|
-
|
|
349
|
+
### WebSocket API
|
|
350
|
+
|
|
351
|
+
All WebSocket methods are accessed via `client.apolloWsSession.*`
|
|
352
|
+
|
|
353
|
+
#### `connect(args?)` - Establish Connection
|
|
354
|
+
Connect to the WebSocket for real-time communication.
|
|
124
355
|
|
|
125
356
|
```typescript
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
357
|
+
const socket = await client.apolloWsSession.connect({
|
|
358
|
+
headers?: Record<string, string>, // Additional headers
|
|
359
|
+
debug?: boolean, // Enable debug mode (default: false)
|
|
360
|
+
reconnectAttempts?: number // Max reconnect attempts (default: 30)
|
|
129
361
|
});
|
|
130
|
-
controller.abort(); // aborts the request
|
|
131
362
|
```
|
|
132
363
|
|
|
133
|
-
|
|
364
|
+
#### Socket Events
|
|
134
365
|
|
|
135
|
-
|
|
136
|
-
The `.withRawResponse()` method returns a promise that results to an object with a `data` and a `rawResponse` property.
|
|
366
|
+
Listen to events using `socket.on(event, callback)`:
|
|
137
367
|
|
|
138
368
|
```typescript
|
|
139
|
-
|
|
369
|
+
// Connection opened
|
|
370
|
+
socket.on('open', () => void);
|
|
371
|
+
|
|
372
|
+
// Message received from agent
|
|
373
|
+
socket.on('message', (message: Response) => void);
|
|
140
374
|
|
|
141
|
-
|
|
142
|
-
|
|
375
|
+
// Error occurred
|
|
376
|
+
socket.on('error', (error: Error) => void);
|
|
377
|
+
|
|
378
|
+
// Connection closed
|
|
379
|
+
socket.on('close', (event: CloseEvent) => void);
|
|
143
380
|
```
|
|
144
381
|
|
|
145
|
-
|
|
382
|
+
**Message Types:**
|
|
383
|
+
- `streaming_update` - Partial response while agent is thinking
|
|
384
|
+
- `final_message` - Complete response with optional product cards
|
|
385
|
+
- `error` - Error message from the agent
|
|
386
|
+
|
|
387
|
+
**New in v0.6.0:**
|
|
388
|
+
- **Message fields:** `welcome_message`, `executed_workflows` - Track workflow execution and welcome messages
|
|
389
|
+
- **Card fields:** `category`, `query`, `sub_entities`, `self_review` - Enhanced product card information with self-review scoring
|
|
390
|
+
- **Product Metadata API:** New endpoint to retrieve metadata from product URLs
|
|
146
391
|
|
|
147
|
-
|
|
392
|
+
#### Socket Methods
|
|
148
393
|
|
|
149
394
|
```typescript
|
|
150
|
-
|
|
395
|
+
// Send a message to the agent
|
|
396
|
+
socket.sendUserMessage({
|
|
397
|
+
task_id: string, // Task identifier
|
|
398
|
+
text: string // Message text
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Close the connection
|
|
402
|
+
socket.close();
|
|
403
|
+
|
|
404
|
+
// Wait for connection to open (returns Promise)
|
|
405
|
+
await socket.waitForOpen();
|
|
406
|
+
|
|
407
|
+
// Check connection state
|
|
408
|
+
const state = socket.readyState;
|
|
409
|
+
// 0 = CONNECTING, 1 = OPEN, 2 = CLOSING, 3 = CLOSED
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## 🎯 Common Use Cases
|
|
413
|
+
|
|
414
|
+
### Complete Example: E-commerce Product Search
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
151
418
|
|
|
152
419
|
const client = new ApolloClient({
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
420
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
async function searchProducts(userId: string, query: string) {
|
|
424
|
+
// Step 1: Create a task
|
|
425
|
+
const taskResponse = await client.controllerApi.createTask({
|
|
426
|
+
user_id: userId,
|
|
427
|
+
task_origin_type: 'web-widget'
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const taskId = taskResponse.id;
|
|
431
|
+
console.log('Created task:', taskId);
|
|
432
|
+
|
|
433
|
+
// Step 2: Connect to WebSocket with authentication
|
|
434
|
+
const socket = await client.apolloWsSession.connect({
|
|
435
|
+
debug: false,
|
|
436
|
+
reconnectAttempts: 3,
|
|
437
|
+
headers: {
|
|
438
|
+
'x-network-api-key': 'API_KEY_YOUR_KEY_HERE'
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Step 3: Set up event handlers
|
|
443
|
+
socket.on('open', () => {
|
|
444
|
+
console.log('Connected! Sending query...');
|
|
445
|
+
socket.sendUserMessage({
|
|
446
|
+
task_id: taskId,
|
|
447
|
+
text: query
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
socket.on('message', (message) => {
|
|
452
|
+
if (message.channel?.eventName === 'thread-message-text-content-updated') {
|
|
453
|
+
// Show real-time updates
|
|
454
|
+
console.log('Agent:', message.data?.text);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (message.id && message.text && message.sender) {
|
|
458
|
+
console.log('\n✅ Final Response:', message.text);
|
|
459
|
+
|
|
460
|
+
// Display product recommendations
|
|
461
|
+
if (message.cards && message.cards.length > 0) {
|
|
462
|
+
console.log('\n🛍️ Product Recommendations:');
|
|
463
|
+
message.cards.forEach((card, index) => {
|
|
464
|
+
console.log(`${index + 1}. ${card.name}`);
|
|
465
|
+
console.log(` Product ID: ${card.id}`);
|
|
466
|
+
if (card.parameters && card.parameters.length > 0) {
|
|
467
|
+
console.log(` Attributes: ${card.parameters.length}`);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Close connection after receiving final response
|
|
473
|
+
socket.close();
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
socket.on('error', (error) => {
|
|
478
|
+
console.error('Error:', error.message);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Usage
|
|
483
|
+
searchProducts('user123', 'I need a gaming laptop under $1500');
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### REST API Only: Check Task Status
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
490
|
+
|
|
491
|
+
const client = new ApolloClient({
|
|
492
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
async function getTaskHistory(userId: string) {
|
|
496
|
+
// Get all tasks for a user
|
|
497
|
+
const tasksResponse = await client.controllerApi.listUserTasks({
|
|
498
|
+
user_id: userId,
|
|
499
|
+
page: 1,
|
|
500
|
+
size: 20
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
console.log(`Found ${tasksResponse.total} tasks`);
|
|
504
|
+
|
|
505
|
+
// Get messages for the most recent task
|
|
506
|
+
if (tasksResponse.tasks && tasksResponse.tasks.length > 0) {
|
|
507
|
+
const latestTask = tasksResponse.tasks[0];
|
|
508
|
+
const messages = await client.controllerApi.getTaskMessages(latestTask.id);
|
|
509
|
+
|
|
510
|
+
console.log(`Task ${latestTask.id} has ${messages.length} messages`);
|
|
511
|
+
messages.forEach(msg => {
|
|
512
|
+
console.log(`[${msg.sender.type}]: ${msg.text}`);
|
|
513
|
+
});
|
|
158
514
|
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getTaskHistory('user123');
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Get Product Metadata
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
524
|
+
|
|
525
|
+
const client = new ApolloClient({
|
|
526
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
159
527
|
});
|
|
528
|
+
|
|
529
|
+
async function fetchProductMetadata(productLink: string) {
|
|
530
|
+
try {
|
|
531
|
+
// Fetch metadata for a product
|
|
532
|
+
const metadata = await client.controllerApi.getProductMetadata({
|
|
533
|
+
link: productLink
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
console.log('Product Metadata:', metadata);
|
|
537
|
+
|
|
538
|
+
// Metadata might include: name, price, description, images, etc.
|
|
539
|
+
if (metadata) {
|
|
540
|
+
console.log('Available fields:', Object.keys(metadata));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return metadata;
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error('Error fetching product metadata:', error);
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Example usage
|
|
551
|
+
fetchProductMetadata('https://www.example.com/product/12345');
|
|
160
552
|
```
|
|
161
|
-
The `logging` object can have the following properties:
|
|
162
|
-
- `level`: The log level to use. Defaults to `logging.LogLevel.Info`.
|
|
163
|
-
- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`.
|
|
164
|
-
- `silent`: Whether to silence the logger. Defaults to `true`.
|
|
165
553
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
554
|
+
### Send Message with Agent Variables (NEW in v1.2.28)
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
558
|
+
|
|
559
|
+
const client = new ApolloClient({
|
|
560
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
async function sendContextualMessage(taskId: string, message: string, userContext: Record<string, unknown>) {
|
|
564
|
+
try {
|
|
565
|
+
// Send a message with custom agent variables for contextual responses
|
|
566
|
+
const response = await client.controllerApi.sendMessage({
|
|
567
|
+
task_id: taskId,
|
|
568
|
+
text: message,
|
|
569
|
+
is_external_api: true,
|
|
570
|
+
agent_variables: userContext
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
console.log('Agent Response:', response.text);
|
|
574
|
+
|
|
575
|
+
// The agent will use the provided context to tailor its response
|
|
576
|
+
if (response.cards && response.cards.length > 0) {
|
|
577
|
+
console.log('Recommended products:', response.cards.length);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return response;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
console.error('Error sending message:', error);
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
171
586
|
|
|
172
|
-
|
|
587
|
+
// Example usage - provide context about user preferences
|
|
588
|
+
sendContextualMessage('task-123', 'What do you recommend?', {
|
|
589
|
+
context: 'User is browsing electric vehicles',
|
|
590
|
+
user_preference: 'eco-friendly',
|
|
591
|
+
budget_range: '$30,000 - $50,000',
|
|
592
|
+
location: 'California'
|
|
593
|
+
});
|
|
594
|
+
```
|
|
173
595
|
|
|
174
|
-
|
|
175
|
-
<summary>Custom logger examples</summary>
|
|
596
|
+
### Get Agent Context (NEW in v1.2.28)
|
|
176
597
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
import winston from 'winston';
|
|
598
|
+
```typescript
|
|
599
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
180
600
|
|
|
181
|
-
const
|
|
601
|
+
const client = new ApolloClient({
|
|
602
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
603
|
+
});
|
|
182
604
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
605
|
+
async function exploreAgentCapabilities() {
|
|
606
|
+
try {
|
|
607
|
+
// Get the agent's context configuration
|
|
608
|
+
const context = await client.controllerApi.getAgentContext({
|
|
609
|
+
task_id: 'your-task-id',
|
|
610
|
+
query: 'test context'
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
console.log('Agent Configuration:');
|
|
614
|
+
console.log(' Title:', context.title);
|
|
615
|
+
console.log(' Static Context:', context.static_context);
|
|
616
|
+
|
|
617
|
+
// Explore available parameters
|
|
618
|
+
if (context.params && context.params.length > 0) {
|
|
619
|
+
console.log('\nAvailable Parameters:');
|
|
620
|
+
context.params.forEach(param => {
|
|
621
|
+
console.log(` - ${param.title}: ${param.param}`);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Explore entities
|
|
626
|
+
if (context.entities && context.entities.length > 0) {
|
|
627
|
+
console.log('\nConfigured Entities:', context.entities.length);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return context;
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error('Error getting agent context:', error);
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
exploreAgentCapabilities();
|
|
189
638
|
```
|
|
190
639
|
|
|
191
|
-
|
|
640
|
+
### Get Direct Followup Suggestions (NEW in v1.2.28)
|
|
192
641
|
|
|
193
|
-
```
|
|
194
|
-
import
|
|
642
|
+
```typescript
|
|
643
|
+
import { ApolloClient } from '@aui.io/aui-client-staging';
|
|
644
|
+
|
|
645
|
+
const client = new ApolloClient({
|
|
646
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
647
|
+
});
|
|
195
648
|
|
|
196
|
-
|
|
649
|
+
async function getSuggestedQuestions(taskId: string) {
|
|
650
|
+
try {
|
|
651
|
+
// Get AI-generated followup suggestions based on conversation context
|
|
652
|
+
const suggestions = await client.controllerApi.getDirectFollowupSuggestions(taskId);
|
|
653
|
+
|
|
654
|
+
console.log('Suggested followup questions:');
|
|
655
|
+
suggestions.forEach((suggestion, index) => {
|
|
656
|
+
console.log(` ${index + 1}. ${suggestion}`);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Use these suggestions to guide the user's next interaction
|
|
660
|
+
return suggestions;
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.error('Error getting suggestions:', error);
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
197
666
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
667
|
+
// Example usage
|
|
668
|
+
getSuggestedQuestions('task-123');
|
|
669
|
+
// Output:
|
|
670
|
+
// Suggested followup questions:
|
|
671
|
+
// 1. "What colors are available?"
|
|
672
|
+
// 2. "Do you offer financing options?"
|
|
673
|
+
// 3. "Can I schedule a test drive?"
|
|
204
674
|
```
|
|
205
|
-
</details>
|
|
206
675
|
|
|
676
|
+
## 🔧 Advanced Configuration
|
|
207
677
|
|
|
208
|
-
###
|
|
678
|
+
### Custom Timeout and Retries
|
|
209
679
|
|
|
680
|
+
```typescript
|
|
681
|
+
const client = new ApolloClient({
|
|
682
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE',
|
|
683
|
+
timeoutInSeconds: 120, // 2 minute timeout
|
|
684
|
+
maxRetries: 5 // Retry up to 5 times
|
|
685
|
+
});
|
|
210
686
|
|
|
211
|
-
|
|
687
|
+
// Per-request overrides
|
|
688
|
+
const taskResponse = await client.controllerApi.createTask(
|
|
689
|
+
{
|
|
690
|
+
user_id: 'user123',
|
|
691
|
+
task_origin_type: 'web-widget'
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
timeoutInSeconds: 30, // Override for this request only
|
|
695
|
+
maxRetries: 2
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
```
|
|
212
699
|
|
|
700
|
+
### WebSocket with Reconnection
|
|
213
701
|
|
|
702
|
+
```typescript
|
|
703
|
+
const socket = await client.apolloWsSession.connect({
|
|
704
|
+
debug: true, // Enable debug logging
|
|
705
|
+
reconnectAttempts: 50, // Try to reconnect up to 50 times
|
|
706
|
+
headers: {
|
|
707
|
+
'x-network-api-key': 'API_KEY_YOUR_KEY_HERE'
|
|
708
|
+
}
|
|
709
|
+
});
|
|
214
710
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
-
|
|
711
|
+
// The WebSocket will automatically attempt to reconnect on failure
|
|
712
|
+
socket.on('close', (event) => {
|
|
713
|
+
console.log(`Connection closed with code ${event.code}`);
|
|
714
|
+
// Socket will auto-reconnect unless you called socket.close()
|
|
715
|
+
});
|
|
716
|
+
```
|
|
221
717
|
|
|
222
|
-
###
|
|
718
|
+
### Error Handling Best Practices
|
|
223
719
|
|
|
224
|
-
|
|
225
|
-
|
|
720
|
+
```typescript
|
|
721
|
+
import { ApolloClient, UnprocessableEntityError, ApolloError } from '@aui.io/aui-client-staging';
|
|
722
|
+
|
|
723
|
+
const client = new ApolloClient({
|
|
724
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE'
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
const taskResponse = await client.controllerApi.createTask({
|
|
729
|
+
user_id: 'user123',
|
|
730
|
+
task_origin_type: 'web-widget'
|
|
731
|
+
});
|
|
732
|
+
} catch (error) {
|
|
733
|
+
if (error instanceof UnprocessableEntityError) {
|
|
734
|
+
// Validation error (422)
|
|
735
|
+
console.error('Validation failed:', error.body);
|
|
736
|
+
} else if (error instanceof ApolloError) {
|
|
737
|
+
// Other API errors
|
|
738
|
+
console.error('API error:', error.statusCode, error.body);
|
|
739
|
+
} else {
|
|
740
|
+
// Network or other errors
|
|
741
|
+
console.error('Unexpected error:', error);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## 📦 TypeScript Support
|
|
747
|
+
|
|
748
|
+
This SDK is written in TypeScript and includes full type definitions. All types are automatically exported:
|
|
226
749
|
|
|
227
750
|
```typescript
|
|
228
|
-
import {
|
|
751
|
+
import {
|
|
752
|
+
ApolloClient,
|
|
753
|
+
// Request types
|
|
754
|
+
CreateExternalTaskRequest,
|
|
755
|
+
SubmitExternalMessageRequest,
|
|
756
|
+
UserMessagePayload,
|
|
757
|
+
// Response types
|
|
758
|
+
CreateExternalTaskResponse,
|
|
759
|
+
ExternalTaskMessage,
|
|
760
|
+
ListExternalTasksResponse,
|
|
761
|
+
StreamingUpdatePayload,
|
|
762
|
+
FinalMessagePayload,
|
|
763
|
+
ErrorMessagePayload,
|
|
764
|
+
// Error types
|
|
765
|
+
ApolloError,
|
|
766
|
+
UnprocessableEntityError
|
|
767
|
+
} from '@aui.io/aui-client-staging';
|
|
768
|
+
|
|
769
|
+
// All methods have full IntelliSense support
|
|
770
|
+
const client = new ApolloClient({
|
|
771
|
+
networkApiKey: 'YOUR_KEY'
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// TypeScript will autocomplete and type-check
|
|
775
|
+
const taskResponse = await client.controllerApi.createTask({ user_id: 'user123' });
|
|
776
|
+
taskResponse.id; // ✅ Fully typed
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
## 🐛 Troubleshooting
|
|
780
|
+
|
|
781
|
+
### WebSocket Connection Issues
|
|
782
|
+
|
|
783
|
+
**Problem:** Connection fails with `1008 Policy Violation` or authentication errors
|
|
784
|
+
|
|
785
|
+
**Solution 1:** Make sure you're using SDK version **1.1.7 or higher**, which includes a fix for Node.js v21+ WebSocket compatibility:
|
|
786
|
+
|
|
787
|
+
```bash
|
|
788
|
+
npm install @aui.io/aui-client-staging@latest
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**Solution 2:** If using an older SDK version, downgrade to Node.js v20:
|
|
792
|
+
|
|
793
|
+
```bash
|
|
794
|
+
# Check your Node version
|
|
795
|
+
node --version
|
|
796
|
+
|
|
797
|
+
# Switch to Node 20 if using nvm
|
|
798
|
+
nvm use 20
|
|
799
|
+
|
|
800
|
+
# Or install Node 20
|
|
801
|
+
nvm install 20
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
**Solution 3:** Verify your API key is being passed correctly:
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
const client = new ApolloClient({
|
|
808
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE' // Make sure this is set
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// Or pass it per-request
|
|
812
|
+
const socket = await client.apolloWsSession.connect({
|
|
813
|
+
headers: {
|
|
814
|
+
'x-network-api-key': 'API_KEY_YOUR_KEY_HERE'
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Authentication Errors (401/403)
|
|
820
|
+
|
|
821
|
+
**Problem:** Getting `401 Unauthorized` or `403 Forbidden` errors
|
|
229
822
|
|
|
823
|
+
**Solution:** Verify your API key:
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
230
826
|
const client = new ApolloClient({
|
|
231
|
-
|
|
232
|
-
fetcher: // provide your implementation here
|
|
827
|
+
networkApiKey: 'API_KEY_YOUR_KEY_HERE' // Double-check this value
|
|
233
828
|
});
|
|
829
|
+
|
|
830
|
+
// The key should start with "API_KEY_"
|
|
831
|
+
// Example: API_KEY_01K------
|
|
234
832
|
```
|
|
235
833
|
|
|
236
|
-
|
|
834
|
+
### CORS Errors (Browser Only)
|
|
835
|
+
|
|
836
|
+
**Problem:** Getting CORS errors when using the SDK in a browser
|
|
837
|
+
|
|
838
|
+
**Solution:** The API must be configured to allow requests from your domain. Contact your API administrator to whitelist your origin.
|
|
839
|
+
|
|
840
|
+
### TypeScript Errors
|
|
841
|
+
|
|
842
|
+
**Problem:** TypeScript compilation errors or missing type definitions
|
|
843
|
+
|
|
844
|
+
**Solution:** Ensure you're using TypeScript 4.0 or higher:
|
|
845
|
+
|
|
846
|
+
```bash
|
|
847
|
+
npm install --save-dev typescript@latest
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
## 📚 Examples
|
|
851
|
+
|
|
852
|
+
The `examples/` directory contains ready-to-run code examples:
|
|
853
|
+
|
|
854
|
+
- **Product Metadata API** - [`examples/test-product-metadata.js`](./examples/test-product-metadata.js)
|
|
855
|
+
- Fetch product information from URLs
|
|
856
|
+
- Handle errors and edge cases
|
|
857
|
+
- Extract and use metadata
|
|
858
|
+
|
|
859
|
+
Run examples:
|
|
860
|
+
```bash
|
|
861
|
+
export NETWORK_API_KEY="API_KEY_YOUR_KEY_HERE"
|
|
862
|
+
node examples/test-product-metadata.js
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
See the [examples README](./examples/README.md) for more details.
|
|
866
|
+
|
|
867
|
+
## 🔗 Resources
|
|
868
|
+
|
|
869
|
+
- **GitHub Repository:** [aui-io/aui-client-staging-typescript](https://github.com/aui-io/aui-client-staging-typescript)
|
|
870
|
+
- **npm Package:** [@aui.io/aui-client-staging](https://www.npmjs.com/package/@aui.io/aui-client-staging)
|
|
871
|
+
- **API Documentation:** [Full API Reference](https://docs.aui.io)
|
|
872
|
+
- **Report Issues:** [GitHub Issues](https://github.com/aui-io/aui-client-staging-typescript/issues)
|
|
873
|
+
|
|
874
|
+
## 📄 License
|
|
875
|
+
|
|
876
|
+
This SDK is proprietary software. Unauthorized copying or distribution is prohibited.
|
|
877
|
+
|
|
878
|
+
## 🤝 Support
|
|
879
|
+
|
|
880
|
+
For support, please contact your AUI representative or open an issue on GitHub.
|
|
881
|
+
|
|
882
|
+
---
|
|
237
883
|
|
|
238
|
-
|
|
239
|
-
Additions made directly to this library would have to be moved over to our generation code,
|
|
240
|
-
otherwise they would be overwritten upon the next generated release. Feel free to open a PR as
|
|
241
|
-
a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
|
|
242
|
-
an issue first to discuss with us!
|
|
884
|
+
**Built with ❤️ by the AUI team**
|
|
243
885
|
|
|
244
|
-
On the other hand, contributions to the README are always very welcome!
|