bytex-sdk 5.1.0 → 5.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.
Files changed (2) hide show
  1. package/index.js +210 -14
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -88,7 +88,7 @@ export class BytexCloud {
88
88
  * ByteX X-RAY v2.5 (Self-Healing Diagnostic)
89
89
  */
90
90
  async xray() {
91
- const report = { timestamp: new Date().toISOString(), sdk_version: '2.1.0', checks: {}, advice: [] };
91
+ const report = { timestamp: new Date().toISOString(), sdk_version: '5.2.0', checks: {}, advice: [] };
92
92
  const { data: { session } } = await this.supabase.auth.getSession();
93
93
 
94
94
  // 1. Connectivity Check
@@ -121,15 +121,197 @@ export class BytexCloud {
121
121
  }
122
122
 
123
123
  _requireKey() { if (!this.apiKey) throw new Error('API Key required!'); }
124
+
125
+ /**
126
+ * Copy a file within the same project under a new name.
127
+ */
128
+ async copy(sourceName, destName) {
129
+ return this._execute(async () => {
130
+ this._requireKey();
131
+ const src = sourceName.endsWith('.btx') ? sourceName : `${sourceName}.stream.btx`;
132
+ const { data: { session } } = await this.supabase.auth.getSession();
133
+ const res = await fetch(`${this.workerUrl}?action=view&key=${this.apiKey}&file=${encodeURIComponent(src)}&_cb=${Date.now()}`, {
134
+ headers: { 'Authorization': `Bearer ${session?.access_token || ''}` }
135
+ });
136
+ if (!res.ok) throw new Error(`File not found: ${sourceName}`);
137
+ const data = await res.arrayBuffer();
138
+ const fd = new FormData();
139
+ fd.append('file', new Blob([data]), destName);
140
+ const upRes = await fetch(`${this.workerUrl}?action=upload&key=${this.apiKey}`, { method: 'POST', body: fd });
141
+ if (!upRes.ok) throw new Error(await upRes.text());
142
+ return { source: sourceName, dest: destName };
143
+ }, 'Copy');
144
+ }
145
+
146
+ /**
147
+ * Move a file to a different project (API key).
148
+ * @param {string} name - File name to move
149
+ * @param {string} destApiKey - Destination project's API key
150
+ */
151
+ async move(name, destApiKey) {
152
+ return this._execute(async () => {
153
+ this._requireKey();
154
+ const src = name.endsWith('.btx') ? name : `${name}.stream.btx`;
155
+ const { data: { session } } = await this.supabase.auth.getSession();
156
+ // Download from source
157
+ const res = await fetch(`${this.workerUrl}?action=view&key=${this.apiKey}&file=${encodeURIComponent(src)}&_cb=${Date.now()}`, {
158
+ headers: { 'Authorization': `Bearer ${session?.access_token || ''}` }
159
+ });
160
+ if (!res.ok) throw new Error(`File not found: ${name}`);
161
+ const data = await res.arrayBuffer();
162
+ // Upload to destination
163
+ const fd = new FormData();
164
+ fd.append('file', new Blob([data]), name);
165
+ await fetch(`${this.workerUrl}?action=upload&key=${destApiKey}`, { method: 'POST', body: fd });
166
+ // Delete from source
167
+ await fetch(`${this.workerUrl}?action=delete&key=${this.apiKey}&file=${encodeURIComponent(src)}`, {
168
+ method: 'DELETE', headers: { 'Authorization': `Bearer ${session?.access_token || ''}` }
169
+ });
170
+ return { moved: name, to: destApiKey };
171
+ }, 'Move');
172
+ }
173
+
174
+ /**
175
+ * Tag a file with one or more labels.
176
+ * @param {string} fileName - The file to tag
177
+ * @param {string[]} tags - Array of tag strings
178
+ */
179
+ async tag(fileName, tags) {
180
+ return this._execute(async () => {
181
+ this._requireKey();
182
+ const existing = await this._getMeta('tags', fileName) || [];
183
+ const merged = [...new Set([...existing, ...tags])];
184
+ await this._setMeta('tags', fileName, merged);
185
+ return { fileName, tags: merged };
186
+ }, 'Tag');
187
+ }
188
+
189
+ async getTags(fileName) {
190
+ return this._execute(async () => {
191
+ this._requireKey();
192
+ return await this._getMeta('tags', fileName) || [];
193
+ }, 'GetTags');
194
+ }
195
+
196
+ async removeTag(fileName, tag) {
197
+ return this._execute(async () => {
198
+ this._requireKey();
199
+ const existing = await this._getMeta('tags', fileName) || [];
200
+ const updated = existing.filter(t => t !== tag);
201
+ await this._setMeta('tags', fileName, updated);
202
+ return { fileName, tags: updated };
203
+ }, 'RemoveTag');
204
+ }
205
+
206
+ /**
207
+ * Pin a file to protect it from bulk operations.
208
+ */
209
+ async pin(fileName) {
210
+ return this._execute(async () => {
211
+ this._requireKey();
212
+ const existing = await this._getMeta('pins', '__global') || [];
213
+ if (!existing.includes(fileName)) {
214
+ await this._setMeta('pins', '__global', [...existing, fileName]);
215
+ }
216
+ return { pinned: fileName };
217
+ }, 'Pin');
218
+ }
219
+
220
+ async unpin(fileName) {
221
+ return this._execute(async () => {
222
+ this._requireKey();
223
+ const existing = await this._getMeta('pins', '__global') || [];
224
+ await this._setMeta('pins', '__global', existing.filter(f => f !== fileName));
225
+ return { unpinned: fileName };
226
+ }, 'Unpin');
227
+ }
228
+
229
+ async getPins() {
230
+ return this._execute(async () => {
231
+ this._requireKey();
232
+ return await this._getMeta('pins', '__global') || [];
233
+ }, 'GetPins');
234
+ }
235
+
236
+ /**
237
+ * Save a versioned snapshot of a file.
238
+ */
239
+ async saveVersion(name) {
240
+ return this._execute(async () => {
241
+ this._requireKey();
242
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
243
+ return await this.copy(name, `__version__${ts}__${name}`);
244
+ }, 'SaveVersion');
245
+ }
246
+
247
+ /**
248
+ * List all saved versions of a file.
249
+ */
250
+ async listVersions(name) {
251
+ return this._execute(async () => {
252
+ this._requireKey();
253
+ const { data: { session } } = await this.supabase.auth.getSession();
254
+ const res = await fetch(`${this.workerUrl}?action=list&key=${this.apiKey}`, {
255
+ headers: { 'Authorization': `Bearer ${session?.access_token || ''}` }
256
+ });
257
+ const text = await res.text();
258
+ const files = text.trim().split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
259
+ return files.filter(f => (f.file_name || '').startsWith('__version__') && (f.file_name || '').endsWith(name));
260
+ }, 'ListVersions');
261
+ }
262
+
263
+ /**
264
+ * Register a webhook URL to receive event notifications.
265
+ * Note: webhooks are stored locally/in Supabase. Your server must listen at the endpoint.
266
+ */
267
+ async addWebhook(url, events = ['upload', 'delete']) {
268
+ return this._execute(async () => {
269
+ this._requireKey();
270
+ const existing = await this._getMeta('webhooks', '__global') || [];
271
+ const updated = [...existing.filter(w => w.url !== url), { url, events, createdAt: new Date().toISOString() }];
272
+ await this._setMeta('webhooks', '__global', updated);
273
+ return { url, events };
274
+ }, 'AddWebhook');
275
+ }
276
+
277
+ async getWebhooks() {
278
+ return this._execute(async () => {
279
+ this._requireKey();
280
+ return await this._getMeta('webhooks', '__global') || [];
281
+ }, 'GetWebhooks');
282
+ }
283
+
284
+ async removeWebhook(url) {
285
+ return this._execute(async () => {
286
+ this._requireKey();
287
+ const existing = await this._getMeta('webhooks', '__global') || [];
288
+ await this._setMeta('webhooks', '__global', existing.filter(w => w.url !== url));
289
+ return { removed: url };
290
+ }, 'RemoveWebhook');
291
+ }
292
+
293
+ // Internal metadata helper using Supabase
294
+ async _getMeta(type, key) {
295
+ try {
296
+ const { data } = await this.supabase.from('bytex_metadata').select('value').eq('api_key', this.apiKey).eq('meta_type', type).eq('meta_key', key).single();
297
+ return data?.value || null;
298
+ } catch { return null; }
299
+ }
300
+
301
+ async _setMeta(type, key, value) {
302
+ await this.supabase.from('bytex_metadata').upsert({ api_key: this.apiKey, meta_type: type, meta_key: key, value }, { onConflict: 'api_key,meta_type,meta_key' });
303
+ }
124
304
  }
125
305
 
126
306
  /**
127
- * ByteX AI (BYOM) v1.0
128
- * Bring Your Own Model — connect your own AI API keys (OpenAI, Claude, Gemini)
307
+ * ByteX AI (BYOM) v1.1
308
+ * Bring Your Own Model — connect your own AI API keys
129
309
  *
130
310
  * Usage:
131
- * const ai = new BytexAI({ openai: 'sk-...', claude: 'sk-ant-...', gemini: 'AIza...' });
132
- * const reply = await ai.chat('openai', 'gpt-4o', [{ role: 'user', content: 'Hello!' }]);
311
+ * const ai = new BytexAI({
312
+ * openai: 'sk-...',
313
+ * custom: { key: '...', endpoint: 'https://api.groq.com/openai/v1' }
314
+ * });
133
315
  */
134
316
  export class BytexAI {
135
317
  constructor(keys = {}) {
@@ -138,10 +320,10 @@ export class BytexAI {
138
320
 
139
321
  /**
140
322
  * Universal chat method.
141
- * @param {'openai'|'claude'|'gemini'} provider - The AI provider
142
- * @param {string} model - The model name (e.g. 'gpt-4o', 'claude-3-5-sonnet-20241022', 'gemini-1.5-pro')
323
+ * @param {'openai'|'claude'|'gemini'|'custom'} provider - The AI provider
324
+ * @param {string} model - The model name
143
325
  * @param {Array<{role: string, content: string}>} messages - Chat messages
144
- * @param {object} options - Optional params (temperature, maxTokens, etc.)
326
+ * @param {object} options - Optional params
145
327
  * @returns {Promise<string>} - The AI reply text
146
328
  */
147
329
  async chat(provider, model, messages, options = {}) {
@@ -149,23 +331,37 @@ export class BytexAI {
149
331
  case 'openai': return this._openai(model, messages, options);
150
332
  case 'claude': return this._claude(model, messages, options);
151
333
  case 'gemini': return this._gemini(model, messages, options);
152
- default: throw new Error(`[BytexAI] Unknown provider: "${provider}". Use 'openai', 'claude', or 'gemini'.`);
334
+ case 'custom': return this._custom(model, messages, options);
335
+ default: throw new Error(`[BytexAI] Unknown provider: "${provider}". Use 'openai', 'claude', 'gemini', or 'custom'.`);
153
336
  }
154
337
  }
155
338
 
156
339
  async _openai(model, messages, options) {
157
- if (!this.keys.openai) throw new Error('[BytexAI] OpenAI API key is missing. Pass it via: new BytexAI({ openai: "sk-..." })');
158
- const res = await fetch('https://api.openai.com/v1/chat/completions', {
340
+ return this._openaiCompatible('https://api.openai.com/v1', this.keys.openai, model || 'gpt-4o-mini', messages, options);
341
+ }
342
+
343
+ async _custom(model, messages, options) {
344
+ const config = this.keys.custom || {};
345
+ if (!config.key || !config.endpoint) {
346
+ throw new Error('[BytexAI] Custom provider requires both "key" and "endpoint" (OpenAI-compatible).');
347
+ }
348
+ return this._openaiCompatible(config.endpoint, config.key, model, messages, options);
349
+ }
350
+
351
+ async _openaiCompatible(baseURL, key, model, messages, options) {
352
+ if (!key) throw new Error(`[BytexAI] API key for ${baseURL} is missing.`);
353
+ const url = baseURL.endsWith('/') ? `${baseURL}chat/completions` : `${baseURL}/chat/completions`;
354
+ const res = await fetch(url, {
159
355
  method: 'POST',
160
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.keys.openai}` },
356
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
161
357
  body: JSON.stringify({
162
- model: model || 'gpt-4o-mini',
358
+ model: model,
163
359
  messages,
164
360
  temperature: options.temperature ?? 0.7,
165
361
  max_tokens: options.maxTokens ?? 1024,
166
362
  })
167
363
  });
168
- if (!res.ok) throw new Error(`[BytexAI] OpenAI error: ${await res.text()}`);
364
+ if (!res.ok) throw new Error(`[BytexAI] API error (${baseURL}): ${await res.text()}`);
169
365
  const data = await res.json();
170
366
  return data.choices[0].message.content;
171
367
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bytex-sdk",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "Official ByteX Cloud SDK with AI (BYOM) support",
5
5
  "main": "index.js",
6
6
  "scripts": {