@j-o-r/hello-dave 0.0.2 → 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 +288 -163
- package/README.md.backup +269 -0
- package/bin/dave.js +165 -0
- package/examples/CodeServer +43 -0
- package/{bin/hdAsk.js → examples/askDave.js} +50 -39
- package/{bin/hdCode.js → examples/codeDave.js} +47 -47
- package/examples/coderev.js +72 -0
- package/examples/daisy.js +177 -0
- package/examples/docsDave.js +119 -0
- package/examples/gpt.js +54 -72
- package/examples/grok.js +47 -68
- package/examples/npmDave.js +175 -0
- package/examples/promptDave.js +112 -0
- package/examples/readmeDave.js +144 -0
- package/examples/spawndave.js +240 -0
- package/examples/todoDave.js +132 -0
- package/lib/API/openai.com/reponses/text.js +12 -18
- package/lib/API/x.ai/collections.js +354 -0
- package/lib/API/x.ai/files.js +218 -0
- package/lib/API/x.ai/responses.js +494 -0
- package/lib/API/x.ai/text.js +1 -1
- package/lib/AgentClient.js +13 -6
- package/lib/AgentManager.js +79 -10
- package/lib/AgentServer.js +45 -21
- package/lib/Cli.js +7 -1
- package/lib/Prompt.js +4 -2
- package/lib/ToolSet.js +2 -1
- package/lib/genericToolset.js +124 -87
- package/lib/index.js +4 -2
- package/lib/wsCli.js +257 -0
- package/lib/wsIO.js +96 -0
- package/package.json +26 -20
- package/types/API/openai.com/reponses/text.d.ts +17 -3
- package/types/API/x.ai/collections.d.ts +167 -0
- package/types/API/x.ai/files.d.ts +84 -0
- package/types/API/x.ai/responses.d.ts +379 -0
- package/types/AgentClient.d.ts +5 -0
- package/types/AgentManager.d.ts +24 -31
- package/types/AgentServer.d.ts +5 -1
- package/types/Prompt.d.ts +4 -2
- package/types/ToolSet.d.ts +1 -0
- package/types/index.d.ts +4 -3
- package/types/wsCli.d.ts +3 -0
- package/types/wsIO.d.ts +26 -0
- package/utils/bars.js +40 -0
- package/utils/clear_sessions.sh +54 -0
- package/{bin/hdInspect.js → utils/format_log.js} +5 -0
- package/utils/list_sessions.sh +46 -0
- package/utils/search_sessions.sh +73 -0
- package/bin/hdClear.js +0 -13
- package/bin/hdConnect.js +0 -230
- package/bin/hdNpm.js +0 -114
- package/bin/hdPrompt.js +0 -108
- package/examples/claude-test.js +0 -89
- package/examples/claude.js +0 -143
- package/examples/gpt_code.js +0 -125
- package/examples/gpt_note_keeping.js +0 -117
- package/examples/grok_code.js +0 -114
- package/examples/grok_note_keeping.js +0 -111
- package/module.md +0 -189
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [api documentation](https://docs.x.ai/docs/guides/using-collections/api)
|
|
3
|
+
*/
|
|
4
|
+
import { GLOBAL } from '../../fafs.js'
|
|
5
|
+
import { request as doRequest } from '@j-o-r/apiserver';
|
|
6
|
+
import FILES from './files.js';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} Collection
|
|
11
|
+
* @property {string} collection_id - Unique identifier for the collection.
|
|
12
|
+
* @property {string} collection_name - Name of the collection.
|
|
13
|
+
* @property {string} created_at - ISO datetime string when the collection was created.
|
|
14
|
+
* @property {string} updated_at
|
|
15
|
+
* @property {Object} index_configuration - Configuration for the indexing model.
|
|
16
|
+
* @property {string} index_configuration.model_name - Name of the embedding model.
|
|
17
|
+
* @property {Object} chunk_configuration - Configuration for chunking documents.
|
|
18
|
+
* @property {Object} chunk_configuration.tokens_configuration - Token-related chunk settings.
|
|
19
|
+
* @property {number} chunk_configuration.tokens_configuration.max_chunk_size_tokens - Maximum tokens per chunk.
|
|
20
|
+
* @property {number} chunk_configuration.tokens_configuration.chunk_overlap_tokens - Overlap tokens between chunks.
|
|
21
|
+
* @property {string} chunk_configuration.tokens_configuration.encoding_name - Token encoding name.
|
|
22
|
+
* @property {boolean} chunk_configuration.strip_whitespace - Whether to strip whitespace.
|
|
23
|
+
* @property {boolean} chunk_configuration.inject_name_into_chunks - Whether to inject name into chunks.
|
|
24
|
+
* @property {number} documents_count - Number of documents in the collection.
|
|
25
|
+
* @property {Array} field_definitions - Array of field definitions.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef fileList
|
|
29
|
+
* @property {File[]} data
|
|
30
|
+
* @property {string|null} pagination_token
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} File
|
|
34
|
+
* @property {number} bytes - File size in bytes.
|
|
35
|
+
* @property {number} created_at - Unix timestamp of creation.
|
|
36
|
+
* @property {number|null} expires_at - Unix timestamp of expiration, or null.
|
|
37
|
+
* @property {string} filename - Path to the file.
|
|
38
|
+
* @property {string} id - Unique file identifier.
|
|
39
|
+
* @property {string} object - Type of object, e.g., 'file'.
|
|
40
|
+
* @property {string} purpose - Purpose of the file.
|
|
41
|
+
*//**
|
|
42
|
+
* @typedef {Object} SearchResult
|
|
43
|
+
* @property {string} document_id
|
|
44
|
+
* @property {string} content
|
|
45
|
+
* @property {Object} metadata
|
|
46
|
+
* @property {number} score
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const API_BASE = 'https://api.x.ai/v1';
|
|
50
|
+
const MANAGEMENT_BASE = 'https://management-api.x.ai/v1';
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
const collectionStorage = new Map();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the default headers for API
|
|
57
|
+
* @returns {object}
|
|
58
|
+
*/
|
|
59
|
+
const getAPIHeaders = () => {
|
|
60
|
+
if (!process.env['XAIKEY']) {
|
|
61
|
+
throw new Error('Missing XAIKEY! export XAIKEY=<XAIKEY>')
|
|
62
|
+
}
|
|
63
|
+
const KEY = process.env['XAIKEY'];
|
|
64
|
+
return {
|
|
65
|
+
'Authorization': `Bearer ${KEY}`
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the default headers for Management API
|
|
71
|
+
* @returns {object}
|
|
72
|
+
*/
|
|
73
|
+
const getManagementHeaders = () => {
|
|
74
|
+
const KEY = process.env['XAI_MANAGEMENT_KEY'] || process.env['XAIKEY'];
|
|
75
|
+
if (!KEY) {
|
|
76
|
+
throw new Error('Missing XAI_MANAGEMENT_KEY or XAIKEY!')
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
'Authorization': `Bearer ${KEY}`
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a collection
|
|
85
|
+
* @param {string} name
|
|
86
|
+
* @returns {Promise<string>} collection_id
|
|
87
|
+
*/
|
|
88
|
+
async function create(name) {
|
|
89
|
+
const url = `${MANAGEMENT_BASE}/collections`;
|
|
90
|
+
const headers = { ...getManagementHeaders(), 'Content-Type': 'application/json' };
|
|
91
|
+
const body = { collection_name: name };
|
|
92
|
+
const res = await doRequest(url, 'POST', headers, body);
|
|
93
|
+
if (res.status !== 200) {
|
|
94
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
95
|
+
}
|
|
96
|
+
return res.response.collection_id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* List collections
|
|
101
|
+
* @returns {Promise<Array<Collection>>}
|
|
102
|
+
*/
|
|
103
|
+
async function list() {
|
|
104
|
+
const url = `${MANAGEMENT_BASE}/collections`;
|
|
105
|
+
const headers = getManagementHeaders();
|
|
106
|
+
const res = await doRequest(url, 'GET', headers, {});
|
|
107
|
+
if (res.status !== 200) {
|
|
108
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
109
|
+
}
|
|
110
|
+
return res.response.collections || [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get collection details
|
|
115
|
+
* @param {string} id
|
|
116
|
+
* @returns {Promise<Collection>}
|
|
117
|
+
*/
|
|
118
|
+
async function get(id) {
|
|
119
|
+
const url = `${MANAGEMENT_BASE}/collections/${id}`;
|
|
120
|
+
const headers = getManagementHeaders();
|
|
121
|
+
const res = await doRequest(url, 'GET', headers, {});
|
|
122
|
+
if (res.status !== 200) {
|
|
123
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
124
|
+
}
|
|
125
|
+
return res.response;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update collection collection name
|
|
130
|
+
* @param {string} id
|
|
131
|
+
* @param {string} name
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
134
|
+
async function update(id, name) {
|
|
135
|
+
const url = `${MANAGEMENT_BASE}/collections/${id}`;
|
|
136
|
+
const headers = { ...getManagementHeaders(), 'Content-Type': 'application/json' };
|
|
137
|
+
const body = { collection_name: name };
|
|
138
|
+
const res = await doRequest(url, 'PUT', headers, body);
|
|
139
|
+
if (res.status !== 200) {
|
|
140
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Delete collection
|
|
146
|
+
* @param {string} id
|
|
147
|
+
* @returns {Promise<void>}
|
|
148
|
+
*/
|
|
149
|
+
async function del (id) {
|
|
150
|
+
const url = `${MANAGEMENT_BASE}/collections/${id}`;
|
|
151
|
+
const headers = getManagementHeaders();
|
|
152
|
+
const res = await doRequest(url, 'DELETE', headers, {});
|
|
153
|
+
if (res.status !== 200) {
|
|
154
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add file to collection
|
|
160
|
+
* @param {string} collectionId
|
|
161
|
+
* @param {string} fileId
|
|
162
|
+
* @returns {Promise<void>}
|
|
163
|
+
*/
|
|
164
|
+
async function addFileToCollection(collectionId, fileId) {
|
|
165
|
+
const url = `${MANAGEMENT_BASE}/collections/${collectionId}/documents/${fileId}`;
|
|
166
|
+
const headers = getManagementHeaders();
|
|
167
|
+
const res = await doRequest(url, 'POST', headers, {});
|
|
168
|
+
if (res.status !== 200) {
|
|
169
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// /**
|
|
174
|
+
// * Upload and add file to collection with optional metadata
|
|
175
|
+
// * @param {string} collectionId
|
|
176
|
+
// * @param {string} filePath
|
|
177
|
+
// * @param {Object} [metadata]
|
|
178
|
+
// * @returns {Promise<string>} file_id
|
|
179
|
+
// */
|
|
180
|
+
// async function uploadAndAddFileToCollection(collectionId, filePath, metadata = {}) {
|
|
181
|
+
// const fileId = (await FILES.upload(filePath)).id;
|
|
182
|
+
// const url = `${MANAGEMENT_BASE}/collections/${collectionId}/documents`;
|
|
183
|
+
// const headers = getManagementHeaders();
|
|
184
|
+
// const formData = new FormData();
|
|
185
|
+
// formData.append('name', path.basename(filePath));
|
|
186
|
+
// formData.append('data', fs.createReadStream(filePath));
|
|
187
|
+
// formData.append('content_type', 'application/octet-stream'); // or detect
|
|
188
|
+
// if (Object.keys(metadata).length > 0) {
|
|
189
|
+
// formData.append('fields', JSON.stringify(metadata));
|
|
190
|
+
// }
|
|
191
|
+
// const response = await fetch(url, {
|
|
192
|
+
// method: 'POST',
|
|
193
|
+
// headers,
|
|
194
|
+
// body: formData
|
|
195
|
+
// });
|
|
196
|
+
// if (!response.ok) {
|
|
197
|
+
// throw new Error(`${response.status}: ${await response.text()}`);
|
|
198
|
+
// }
|
|
199
|
+
// const data = await response.json();
|
|
200
|
+
// return fileId;
|
|
201
|
+
// }
|
|
202
|
+
|
|
203
|
+
// /**
|
|
204
|
+
// * Add files from folder to collection
|
|
205
|
+
// * @param {string} collectionId
|
|
206
|
+
// * @param {string} folderPath
|
|
207
|
+
// * @param {boolean} [recursive=false]
|
|
208
|
+
// * @param {Object} [metadata]
|
|
209
|
+
// * @returns {Promise<string[]>} file_ids
|
|
210
|
+
// */
|
|
211
|
+
// async function addFilesFromFolder(collectionId, folderPath, recursive = false, metadata = {}) {
|
|
212
|
+
// const files = [];
|
|
213
|
+
// const readDir = (dir) => {
|
|
214
|
+
// const items = fs.readdirSync(dir);
|
|
215
|
+
// for (const item of items) {
|
|
216
|
+
// const fullPath = path.join(dir, item);
|
|
217
|
+
// const stat = fs.statSync(fullPath);
|
|
218
|
+
// if (stat.isDirectory() && recursive) {
|
|
219
|
+
// readDir(fullPath);
|
|
220
|
+
// } else if (stat.isFile()) {
|
|
221
|
+
// files.push(fullPath);
|
|
222
|
+
// }
|
|
223
|
+
// }
|
|
224
|
+
// };
|
|
225
|
+
// readDir(folderPath);
|
|
226
|
+
// const fileIds = [];
|
|
227
|
+
// for (const filePath of files) {
|
|
228
|
+
// try {
|
|
229
|
+
// const fileId = await uploadAndAddFileToCollection(collectionId, filePath, metadata);
|
|
230
|
+
// fileIds.push(fileId);
|
|
231
|
+
// } catch (error) {
|
|
232
|
+
// console.error(`Failed to upload ${filePath}: ${error.message}`);
|
|
233
|
+
// }
|
|
234
|
+
// }
|
|
235
|
+
// return fileIds;
|
|
236
|
+
// }
|
|
237
|
+
|
|
238
|
+
// /**
|
|
239
|
+
// * Resync folder to collection (delete all and re-add)
|
|
240
|
+
// * @param {string} collectionId
|
|
241
|
+
// * @param {string} folderPath
|
|
242
|
+
// * @param {boolean} [recursive=false]
|
|
243
|
+
// * @param {Object} [metadata]
|
|
244
|
+
// * @returns {Promise<string[]>} file_ids
|
|
245
|
+
// */
|
|
246
|
+
// async function resyncFolder(collectionId, folderPath, recursive = false, metadata = {}) {
|
|
247
|
+
// // First, delete all files in collection? But API doesn't have list files in collection directly.
|
|
248
|
+
// // Perhaps list collections and delete them, but complicated.
|
|
249
|
+
// // For simplicity, assume we don't delete, just add new.
|
|
250
|
+
// // To resync, perhaps delete collection and recreate, but that would lose id.
|
|
251
|
+
// // Since no direct way, maybe just add new files.
|
|
252
|
+
// // Perhaps get collection details, but doesn't list files.
|
|
253
|
+
// // For now, just add files, assuming no duplicates.
|
|
254
|
+
// return addFilesFromFolder(collectionId, folderPath, recursive, metadata);
|
|
255
|
+
// }
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Delete file from collection
|
|
259
|
+
* @param {string} collectionId
|
|
260
|
+
* @param {string} fileId
|
|
261
|
+
* @returns {Promise<void>}
|
|
262
|
+
*/
|
|
263
|
+
async function deleteFileFromCollection(collectionId, fileId) {
|
|
264
|
+
const url = `${MANAGEMENT_BASE}/collections/${collectionId}/documents/${fileId}`;
|
|
265
|
+
const headers = getManagementHeaders();
|
|
266
|
+
const res = await doRequest(url, 'DELETE', headers, {});
|
|
267
|
+
if (res.status !== 200) {
|
|
268
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The status of files in a collection
|
|
274
|
+
* @param {string} collectionId
|
|
275
|
+
* @returns {Promise<void>}
|
|
276
|
+
*/
|
|
277
|
+
async function status(collectionId) {
|
|
278
|
+
const url = `${MANAGEMENT_BASE}/collections/${collectionId}/documents`;
|
|
279
|
+
const headers = getManagementHeaders();
|
|
280
|
+
const res = await doRequest(url, 'GET', headers, {});
|
|
281
|
+
if (res.status !== 200) {
|
|
282
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
283
|
+
}
|
|
284
|
+
return res.response
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The status of files in a collection
|
|
289
|
+
* @param {string} collectionId
|
|
290
|
+
* @param {string} fileId
|
|
291
|
+
* @returns {Promise<void>}
|
|
292
|
+
*/
|
|
293
|
+
async function details(collectionId,fileId) {
|
|
294
|
+
const url = `${MANAGEMENT_BASE}/collections/${collectionId}/documents/${fileId}`;
|
|
295
|
+
const headers = getManagementHeaders();
|
|
296
|
+
const res = await doRequest(url, 'GET', headers, {});
|
|
297
|
+
if (res.status !== 200) {
|
|
298
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
299
|
+
}
|
|
300
|
+
return res.response
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Search in collections
|
|
304
|
+
* @param {string} query
|
|
305
|
+
* @param {string[]} collectionIds
|
|
306
|
+
* @param {Object} [options]
|
|
307
|
+
* @param {string} [options.retrieval_mode] - e.g., 'hybrid'
|
|
308
|
+
* @returns {Promise<SearchResult[]>}
|
|
309
|
+
*/
|
|
310
|
+
async function searchInCollections(query, collectionIds, options = {}) {
|
|
311
|
+
const url = `${API_BASE}/documents/search`;
|
|
312
|
+
const headers = { ...getAPIHeaders(), 'Content-Type': 'application/json' };
|
|
313
|
+
const body = {
|
|
314
|
+
query,
|
|
315
|
+
source: {
|
|
316
|
+
collection_ids: collectionIds
|
|
317
|
+
},
|
|
318
|
+
...options
|
|
319
|
+
};
|
|
320
|
+
const res = await doRequest(url, 'POST', headers, body);
|
|
321
|
+
if (res.status !== 200) {
|
|
322
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
323
|
+
}
|
|
324
|
+
return res.response.results || res.response;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @returns {Promise<Map<string, Collection>>}
|
|
329
|
+
*/
|
|
330
|
+
const getStorage = async () => {
|
|
331
|
+
const lst = await list();
|
|
332
|
+
let i = 0;
|
|
333
|
+
const len = lst.length;
|
|
334
|
+
for(;i < len; i++) {
|
|
335
|
+
const col = lst[i];
|
|
336
|
+
// Decided to have the name as a key.
|
|
337
|
+
collectionStorage.set(col.collection_name, col);
|
|
338
|
+
}
|
|
339
|
+
return collectionStorage;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export default {
|
|
343
|
+
create,
|
|
344
|
+
list,
|
|
345
|
+
get,
|
|
346
|
+
status,
|
|
347
|
+
details,
|
|
348
|
+
update,
|
|
349
|
+
del,
|
|
350
|
+
addFileToCollection,
|
|
351
|
+
deleteFileFromCollection,
|
|
352
|
+
searchInCollections,
|
|
353
|
+
storage: getStorage
|
|
354
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* https://docs.x.ai/docs/guides/files
|
|
3
|
+
Limitations
|
|
4
|
+
File size: Maximum 48 MB per file
|
|
5
|
+
No batch requests: File attachments with document search are agentic requests and do not support batch mode (n > 1)
|
|
6
|
+
Agentic models only: Requires models that support agentic tool calling (e.g., grok-4-fast, grok-4)
|
|
7
|
+
Supported file formats:
|
|
8
|
+
Plain text files (.txt)
|
|
9
|
+
Markdown files (.md)
|
|
10
|
+
Code files (.py, .js, .java, etc.)
|
|
11
|
+
CSV files (.csv)
|
|
12
|
+
JSON files (.json)
|
|
13
|
+
PDF documents (.pdf)
|
|
14
|
+
And many other text-based formats
|
|
15
|
+
|
|
16
|
+
*/
|
|
17
|
+
import { request as doRequest } from '@j-o-r/apiserver';
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const fileStorage = new Map();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef fileList
|
|
25
|
+
* @property {File[]} data
|
|
26
|
+
* @property {string|null} pagination_token
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} File
|
|
30
|
+
* @property {number} bytes - File size in bytes.
|
|
31
|
+
* @property {number} created_at - Unix timestamp of creation.
|
|
32
|
+
* @property {number|null} expires_at - Unix timestamp of expiration, or null.
|
|
33
|
+
* @property {string} filename - Path to the file.
|
|
34
|
+
* @property {string} id - Unique file identifier.
|
|
35
|
+
* @property {string} object - Type of object, e.g., 'file'.
|
|
36
|
+
* @property {string} purpose - Purpose of the file.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const API_BASE = 'https://api.x.ai/v1';
|
|
40
|
+
/**
|
|
41
|
+
* Is buffer filled with text?
|
|
42
|
+
* @param {Buffer} buffer
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function isTextBuffer(buffer) {
|
|
46
|
+
const text = buffer.toString('utf8');
|
|
47
|
+
const nonPrintable = text.replace(/[\x20-\x7E\t\r\n]/g, '').length;
|
|
48
|
+
return nonPrintable / buffer.length < 0.3; // 30% threshold
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the default headers for API
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
const getAPIHeaders = () => {
|
|
55
|
+
if (!process.env['XAIKEY']) {
|
|
56
|
+
throw new Error('Missing XAIKEY! export XAIKEY=<XAIKEY>')
|
|
57
|
+
}
|
|
58
|
+
const KEY = process.env['XAIKEY'];
|
|
59
|
+
return {
|
|
60
|
+
'Authorization': `Bearer ${KEY}`
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the default headers for Management API
|
|
66
|
+
* @returns {object}
|
|
67
|
+
*/
|
|
68
|
+
const getManagementHeaders = () => {
|
|
69
|
+
const KEY = process.env['XAI_MANAGEMENT_KEY'] || process.env['XAIKEY'];
|
|
70
|
+
if (!KEY) {
|
|
71
|
+
throw new Error('Missing XAI_MANAGEMENT_KEY or XAIKEY!')
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
'Authorization': `Bearer ${KEY}`
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* List Files
|
|
80
|
+
* @param {number} limit
|
|
81
|
+
* @param {'asc'|'desc'} order
|
|
82
|
+
* @param {'created_at'|'filename'| 'size'} sort_by
|
|
83
|
+
* @param {string} page (pagination_token)
|
|
84
|
+
* @retuns {Promise<fileList>}
|
|
85
|
+
*/
|
|
86
|
+
async function list(limit = 100, order = 'asc', sort_by = 'size', page = '') {
|
|
87
|
+
const url = `${API_BASE}/files?limit=${limit}&order=${order}&sort_by=${sort_by}&pagination_token=${page}`;
|
|
88
|
+
const headers = getAPIHeaders();
|
|
89
|
+
const res = await doRequest(url,
|
|
90
|
+
'GET',
|
|
91
|
+
headers
|
|
92
|
+
);
|
|
93
|
+
if (res.status !== 200) {
|
|
94
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
95
|
+
}
|
|
96
|
+
return res.response;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Upload a file
|
|
100
|
+
* When content is filled this will be used as file content
|
|
101
|
+
* @param {string} filePath
|
|
102
|
+
* @param {string} [content] - optional content for a non existing file
|
|
103
|
+
* @returns {Promise<File>} file_id
|
|
104
|
+
*/
|
|
105
|
+
async function upload(filePath, content) {
|
|
106
|
+
const url = `${API_BASE}/files`;
|
|
107
|
+
const headers = getAPIHeaders();
|
|
108
|
+
const formData = new FormData();
|
|
109
|
+
let buffer;
|
|
110
|
+
if (!content) {
|
|
111
|
+
buffer = fs.readFileSync(filePath);
|
|
112
|
+
} else {
|
|
113
|
+
buffer = Buffer.from(content, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
if (buffer.length > 50331648) {
|
|
116
|
+
throw new Error('File too large: exceeds 48 MB limit');
|
|
117
|
+
}
|
|
118
|
+
if (!isTextBuffer(buffer)) {
|
|
119
|
+
throw new Error('File is not plain text based');
|
|
120
|
+
}
|
|
121
|
+
formData.append('file', new Blob([buffer]), filePath);
|
|
122
|
+
const result = await doRequest(url,
|
|
123
|
+
'POST',
|
|
124
|
+
headers,
|
|
125
|
+
formData
|
|
126
|
+
);
|
|
127
|
+
if (result.status < 200 || result.status >= 300) {
|
|
128
|
+
throw new Error(`${result.status}: ${result.response}`);
|
|
129
|
+
}
|
|
130
|
+
const data = result.response;
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get file content
|
|
136
|
+
* @param {string} fileId
|
|
137
|
+
* @returns {Promise<*>}
|
|
138
|
+
*/
|
|
139
|
+
async function get(fileId) {
|
|
140
|
+
const url = `${API_BASE}/files/${fileId}/content`;
|
|
141
|
+
const headers = getAPIHeaders();
|
|
142
|
+
const res = await doRequest(url,
|
|
143
|
+
'GET',
|
|
144
|
+
headers
|
|
145
|
+
);
|
|
146
|
+
if (res.status !== 200) {
|
|
147
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
148
|
+
}
|
|
149
|
+
if (res.responseType === 'blob') {
|
|
150
|
+
return res.response.text();
|
|
151
|
+
}
|
|
152
|
+
return res.response;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get file meta data
|
|
157
|
+
* @param {string} fileId
|
|
158
|
+
* @returns {Promise<File>}
|
|
159
|
+
*/
|
|
160
|
+
async function getMeta(fileId) {
|
|
161
|
+
const url = `${API_BASE}/files/${fileId}`;
|
|
162
|
+
const headers = getAPIHeaders();
|
|
163
|
+
const res = await doRequest(url,
|
|
164
|
+
'GET',
|
|
165
|
+
headers
|
|
166
|
+
);
|
|
167
|
+
if (res.status !== 200) {
|
|
168
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
169
|
+
}
|
|
170
|
+
return res.response;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Remove a file
|
|
175
|
+
* @param {string} fileId
|
|
176
|
+
* @returns {Promise<void>}
|
|
177
|
+
*/
|
|
178
|
+
async function rm(fileId) {
|
|
179
|
+
const url = `${API_BASE}/files/${fileId}`;
|
|
180
|
+
const headers = getAPIHeaders();
|
|
181
|
+
const res = await doRequest(url,
|
|
182
|
+
'DELETE',
|
|
183
|
+
headers
|
|
184
|
+
);
|
|
185
|
+
if (res.status !== 200) {
|
|
186
|
+
throw new Error(`${res.status}: ${JSON.stringify(res.response)}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get all on-line files
|
|
193
|
+
* @returns {Promise<Map<string, File>>}
|
|
194
|
+
*
|
|
195
|
+
*/
|
|
196
|
+
const getStorage = async (page = '') => {
|
|
197
|
+
if (!page) page = '';
|
|
198
|
+
const lst = await list(100, 'asc', 'size', page);
|
|
199
|
+
let i = 0;
|
|
200
|
+
const len = lst.data.length;
|
|
201
|
+
for (; i < len; i++) {
|
|
202
|
+
// console.log(list.data[i]);
|
|
203
|
+
fileStorage.set(lst.data[i].filename, lst.data[i]);
|
|
204
|
+
}
|
|
205
|
+
if (lst.pagination_token) {
|
|
206
|
+
return await getStorage(lst.pagination_token);
|
|
207
|
+
}
|
|
208
|
+
return fileStorage;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default {
|
|
212
|
+
list,
|
|
213
|
+
upload,
|
|
214
|
+
get,
|
|
215
|
+
getMeta,
|
|
216
|
+
rm,
|
|
217
|
+
storage: getStorage
|
|
218
|
+
};
|