@emilshirokikh/slyos-sdk 1.2.2 → 1.3.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.
@@ -0,0 +1,506 @@
1
+ #!/bin/bash
2
+
3
+ #################################################################################
4
+ # Slyos Chatbot Setup Script
5
+ #
6
+ # This script creates a fully functional interactive chatbot using the Slyos SDK.
7
+ # Supports both Mac and Windows (via bash/powershell).
8
+ #
9
+ # Usage:
10
+ # ./create-chatbot.sh [--api-key YOUR_KEY] [--model MODEL_NAME]
11
+ #
12
+ # Examples:
13
+ # ./create-chatbot.sh
14
+ # ./create-chatbot.sh --api-key sk_123456789 --model quantum-1.7b
15
+ #################################################################################
16
+
17
+ set -e
18
+
19
+ # Color codes for terminal output
20
+ RED='\033[0;31m'
21
+ GREEN='\033[0;32m'
22
+ YELLOW='\033[1;33m'
23
+ BLUE='\033[0;34m'
24
+ CYAN='\033[0;36m'
25
+ NC='\033[0m' # No Color
26
+
27
+ # Default values
28
+ API_KEY=""
29
+ MODEL="quantum-1.7b"
30
+ SLYOS_SERVER="https://slyos-prod.eba-qjz3cmgq.us-east-2.elasticbeanstalk.com"
31
+ PROJECT_NAME="slyos-chatbot"
32
+
33
+ #################################################################################
34
+ # Helper Functions
35
+ #################################################################################
36
+
37
+ print_header() {
38
+ echo -e "\n${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
39
+ echo -e "${BLUE}║${NC} ${CYAN}Slyos Interactive Chatbot Setup${NC} ${BLUE}║${NC}"
40
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\n"
41
+ }
42
+
43
+ print_step() {
44
+ echo -e "${CYAN}▶${NC} $1"
45
+ }
46
+
47
+ print_success() {
48
+ echo -e "${GREEN}✓${NC} $1"
49
+ }
50
+
51
+ print_error() {
52
+ echo -e "${RED}✗${NC} $1" >&2
53
+ }
54
+
55
+ print_info() {
56
+ echo -e "${YELLOW}ℹ${NC} $1"
57
+ }
58
+
59
+ #################################################################################
60
+ # Argument Parsing
61
+ #################################################################################
62
+
63
+ while [[ $# -gt 0 ]]; do
64
+ case $1 in
65
+ --api-key)
66
+ API_KEY="$2"
67
+ shift 2
68
+ ;;
69
+ --model)
70
+ MODEL="$2"
71
+ shift 2
72
+ ;;
73
+ -h|--help)
74
+ echo "Usage: $0 [OPTIONS]"
75
+ echo ""
76
+ echo "Options:"
77
+ echo " --api-key KEY Slyos API key (prompted if not provided)"
78
+ echo " --model MODEL AI model to use (default: quantum-1.7b)"
79
+ echo " -h, --help Show this help message"
80
+ exit 0
81
+ ;;
82
+ *)
83
+ print_error "Unknown option: $1"
84
+ exit 1
85
+ ;;
86
+ esac
87
+ done
88
+
89
+ #################################################################################
90
+ # Main Setup
91
+ #################################################################################
92
+
93
+ print_header
94
+
95
+ # Prompt for API key if not provided
96
+ if [ -z "$API_KEY" ]; then
97
+ if [ -t 0 ]; then
98
+ print_step "Enter your Slyos API key (or press Enter for placeholder)"
99
+ read -r -p " API Key: " API_KEY
100
+ fi
101
+
102
+ if [ -z "$API_KEY" ]; then
103
+ API_KEY="YOUR_API_KEY"
104
+ print_info "Using placeholder API key — set SLYOS_API_KEY in .env later"
105
+ else
106
+ print_success "API key configured"
107
+ fi
108
+ else
109
+ print_success "API key provided via arguments"
110
+ fi
111
+
112
+ # Confirm model selection
113
+ print_step "AI Model Configuration"
114
+ echo -e " Current model: ${YELLOW}${MODEL}${NC}"
115
+
116
+ # Only prompt interactively if stdin is a terminal (not piped)
117
+ if [ -t 0 ]; then
118
+ read -p " Use this model? (y/n, default: y): " -r -n 1
119
+ echo
120
+ if [[ ! $REPLY =~ ^[Yy]?$ ]]; then
121
+ read -p " Enter model name: " -r MODEL
122
+ fi
123
+ fi
124
+ print_success "Model configured: ${YELLOW}${MODEL}${NC}"
125
+
126
+ # Check if project already exists
127
+ if [ -d "$PROJECT_NAME" ]; then
128
+ if [ -t 0 ]; then
129
+ print_error "Project folder '$PROJECT_NAME' already exists!"
130
+ read -p " Remove existing folder and continue? (y/n): " -r -n 1
131
+ echo
132
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
133
+ rm -rf "$PROJECT_NAME"
134
+ print_success "Existing folder removed"
135
+ else
136
+ print_error "Setup cancelled"
137
+ exit 1
138
+ fi
139
+ else
140
+ # Non-interactive: auto-remove
141
+ rm -rf "$PROJECT_NAME"
142
+ print_success "Existing folder removed"
143
+ fi
144
+ fi
145
+
146
+ # Create project directory
147
+ print_step "Creating project directory: ${CYAN}$PROJECT_NAME${NC}"
148
+ mkdir -p "$PROJECT_NAME"
149
+ cd "$PROJECT_NAME"
150
+ print_success "Project directory created"
151
+
152
+ # Initialize npm
153
+ print_step "Initializing npm package"
154
+ npm init -y > /dev/null 2>&1
155
+ print_success "npm initialized"
156
+
157
+ # Update package.json to use ES modules
158
+ print_step "Configuring ES module support"
159
+ cat > package.json << 'EOF'
160
+ {
161
+ "name": "slyos-chatbot",
162
+ "version": "1.0.0",
163
+ "description": "Interactive chatbot powered by Slyos SDK",
164
+ "main": "app.mjs",
165
+ "type": "module",
166
+ "scripts": {
167
+ "start": "node app.mjs",
168
+ "chat": "node app.mjs"
169
+ },
170
+ "keywords": ["chatbot", "slyos", "ai"],
171
+ "author": "",
172
+ "license": "MIT"
173
+ }
174
+ EOF
175
+ print_success "Package configuration updated"
176
+
177
+ # Install Slyos SDK + dotenv
178
+ print_step "Installing dependencies"
179
+ print_info "This may take a moment..."
180
+ npm install @emilshirokikh/slyos-sdk dotenv > /dev/null 2>&1
181
+ print_success "Dependencies installed"
182
+
183
+ # Create the chatbot application
184
+ print_step "Creating interactive chatbot application: ${CYAN}app.mjs${NC}"
185
+
186
+ cat > app.mjs << 'CHATBOT_EOF'
187
+ #!/usr/bin/env node
188
+
189
+ import 'dotenv/config';
190
+ import readline from 'readline';
191
+ import SlyOS from '@emilshirokikh/slyos-sdk';
192
+
193
+ // Color codes for terminal output
194
+ const colors = {
195
+ reset: '\x1b[0m',
196
+ bright: '\x1b[1m',
197
+ dim: '\x1b[2m',
198
+ cyan: '\x1b[36m',
199
+ green: '\x1b[32m',
200
+ yellow: '\x1b[33m',
201
+ blue: '\x1b[34m',
202
+ red: '\x1b[31m',
203
+ magenta: '\x1b[35m'
204
+ };
205
+
206
+ // Configuration
207
+ const config = {
208
+ apiKey: process.env.SLYOS_API_KEY || 'YOUR_API_KEY',
209
+ model: process.env.SLYOS_MODEL || 'quantum-1.7b',
210
+ server: process.env.SLYOS_SERVER || 'https://slyos-prod.eba-qjz3cmgq.us-east-2.elasticbeanstalk.com'
211
+ };
212
+
213
+ // Initialize SlyOS SDK
214
+ let sdk;
215
+ try {
216
+ sdk = new SlyOS({
217
+ apiKey: config.apiKey,
218
+ onProgress: (e) => console.log(`${colors.dim}[${e.progress}%] ${e.message}${colors.reset}`)
219
+ });
220
+ } catch (error) {
221
+ console.error(`${colors.red}Error initializing SDK:${colors.reset}`, error.message);
222
+ process.exit(1);
223
+ }
224
+
225
+ // Create readline interface
226
+ const rl = readline.createInterface({
227
+ input: process.stdin,
228
+ output: process.stdout,
229
+ terminal: true
230
+ });
231
+
232
+ // Note: conversation history is not used for generation with small models
233
+ // They work better with single prompts
234
+
235
+ /**
236
+ * Print welcome banner
237
+ */
238
+ function printWelcome() {
239
+ console.clear();
240
+ console.log(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
241
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
242
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}Welcome to the Slyos Interactive Chatbot${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
243
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
244
+ console.log(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}\n`);
245
+
246
+ console.log(`${colors.blue}Model:${colors.reset} ${colors.yellow}${config.model}${colors.reset}`);
247
+ console.log(`${colors.blue}Server:${colors.reset} ${colors.yellow}${config.server}${colors.reset}`);
248
+ if (config.apiKey === 'YOUR_API_KEY') {
249
+ console.log(`${colors.red}⚠ Using placeholder API key - set SLYOS_API_KEY environment variable${colors.reset}`);
250
+ }
251
+ console.log(`\n${colors.bright}Commands:${colors.reset}`);
252
+ console.log(` ${colors.green}Type your message and press Enter to chat${colors.reset}`);
253
+ console.log(` ${colors.green}Type 'clear' to clear conversation history${colors.reset}`);
254
+ console.log(` ${colors.green}Type 'exit' or 'quit' to end the session${colors.reset}`);
255
+ console.log(`\n${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
256
+ }
257
+
258
+ /**
259
+ * Send message to AI and get response
260
+ */
261
+ async function sendMessage(userMessage) {
262
+ try {
263
+ console.log(`${colors.dim}Thinking...${colors.reset}`);
264
+
265
+ // Use chatCompletion (OpenAI-compatible) — handles prompt formatting for any model
266
+ const response = await sdk.chatCompletion(config.model, {
267
+ messages: [
268
+ { role: 'system', content: 'You are a helpful AI assistant. Give short, direct answers.' },
269
+ { role: 'user', content: userMessage }
270
+ ],
271
+ max_tokens: 200,
272
+ temperature: 0.7
273
+ });
274
+
275
+ let assistantMessage = response?.choices?.[0]?.message?.content || '';
276
+
277
+ // Light cleanup — stop at any hallucinated role prefixes
278
+ assistantMessage = assistantMessage
279
+ .split(/\n\s*(User|Human|System):/i)[0]
280
+ .trim();
281
+
282
+ if (!assistantMessage) {
283
+ assistantMessage = '(No response generated — try rephrasing your question)';
284
+ }
285
+
286
+ console.log(`\n${colors.bright}${colors.magenta}AI:${colors.reset} ${assistantMessage}\n`);
287
+ } catch (error) {
288
+ console.error(`\n${colors.red}Error:${colors.reset} ${error.message}\n`);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Prompt user for input
294
+ */
295
+ function promptUser() {
296
+ rl.question(`${colors.bright}${colors.green}You:${colors.reset} `, async (input) => {
297
+ const message = input.trim();
298
+
299
+ if (!message) {
300
+ promptUser();
301
+ return;
302
+ }
303
+
304
+ // Handle commands
305
+ if (message.toLowerCase() === 'exit' || message.toLowerCase() === 'quit') {
306
+ console.log(`\n${colors.bright}${colors.cyan}Thank you for chatting! Goodbye.${colors.reset}\n`);
307
+ rl.close();
308
+ process.exit(0);
309
+ }
310
+
311
+ if (message.toLowerCase() === 'clear') {
312
+ console.clear();
313
+ printWelcome();
314
+ console.log(`${colors.green}✓ Screen cleared${colors.reset}\n`);
315
+ promptUser();
316
+ return;
317
+ }
318
+
319
+ // Send message to AI
320
+ await sendMessage(message);
321
+ promptUser();
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Main entry point
327
+ */
328
+ async function main() {
329
+ printWelcome();
330
+
331
+ try {
332
+ console.log(`${colors.cyan}Initializing SlyOS...${colors.reset}`);
333
+ await sdk.initialize();
334
+
335
+ console.log(`${colors.cyan}Loading model: ${config.model}...${colors.reset}`);
336
+ await sdk.loadModel(config.model);
337
+
338
+ console.log(`${colors.green}Ready! Start chatting below.${colors.reset}\n`);
339
+ console.log(`${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
340
+ } catch (error) {
341
+ console.error(`${colors.red}Failed to initialize: ${error.message}${colors.reset}`);
342
+ console.error(`${colors.dim}Make sure your API key is correct and you have internet access.${colors.reset}`);
343
+ process.exit(1);
344
+ }
345
+
346
+ promptUser();
347
+ }
348
+
349
+ // Handle process termination gracefully
350
+ process.on('SIGINT', () => {
351
+ console.log(`\n${colors.bright}${colors.cyan}Session ended. Goodbye!${colors.reset}\n`);
352
+ rl.close();
353
+ process.exit(0);
354
+ });
355
+
356
+ process.on('SIGTERM', () => {
357
+ rl.close();
358
+ process.exit(0);
359
+ });
360
+
361
+ // Start the chatbot
362
+ main();
363
+ CHATBOT_EOF
364
+
365
+ chmod +x app.mjs
366
+ print_success "Chatbot application created"
367
+
368
+ # Create .env.example file
369
+ print_step "Creating environment configuration example"
370
+ cat > .env.example << 'ENV_EOF'
371
+ # Slyos SDK Configuration
372
+ SLYOS_API_KEY=your_api_key_here
373
+ SLYOS_MODEL=quantum-1.7b
374
+ SLYOS_SERVER=https://slyos-prod.eba-qjz3cmgq.us-east-2.elasticbeanstalk.com
375
+ ENV_EOF
376
+ print_success "Environment configuration template created"
377
+
378
+ # Create README
379
+ print_step "Creating README documentation"
380
+ cat > README.md << 'README_EOF'
381
+ # Slyos Interactive Chatbot
382
+
383
+ A simple yet powerful interactive chatbot powered by the Slyos SDK.
384
+
385
+ ## Features
386
+
387
+ - Interactive command-line interface with colored output
388
+ - Conversation history management
389
+ - Easy API configuration
390
+ - Cross-platform support (Mac, Windows, Linux)
391
+
392
+ ## Installation
393
+
394
+ 1. Clone or download this project
395
+ 2. Install dependencies: `npm install`
396
+ 3. Configure your API key (see Configuration)
397
+
398
+ ## Configuration
399
+
400
+ ### Environment Variables
401
+
402
+ Set these environment variables before running:
403
+
404
+ ```bash
405
+ export SLYOS_API_KEY=your_api_key_here
406
+ export SLYOS_MODEL=quantum-1.7b
407
+ export SLYOS_SERVER=https://slyos-prod.eba-qjz3cmgq.us-east-2.elasticbeanstalk.com
408
+ ```
409
+
410
+ Or create a `.env` file based on `.env.example`.
411
+
412
+ ## Running the Chatbot
413
+
414
+ ### Direct Method
415
+ ```bash
416
+ npm start
417
+ ```
418
+
419
+ ### With Environment Variables
420
+ ```bash
421
+ SLYOS_API_KEY=your_key npm start
422
+ ```
423
+
424
+ ### Manual
425
+ ```bash
426
+ node app.mjs
427
+ ```
428
+
429
+ ## Usage
430
+
431
+ Once the chatbot starts:
432
+
433
+ - **Chat**: Type your message and press Enter
434
+ - **Clear History**: Type `clear` to reset conversation
435
+ - **Exit**: Type `exit` or `quit` to end session
436
+ - **Interrupt**: Press Ctrl+C to exit anytime
437
+
438
+ ## API Response Format
439
+
440
+ The chatbot supports multiple response formats from the SDK:
441
+
442
+ - `response.content` - Primary response text
443
+ - `response.text` - Alternative response field
444
+ - Direct string response - Fallback format
445
+
446
+ ## Troubleshooting
447
+
448
+ ### "Error initializing SDK"
449
+ - Check that your API key is valid
450
+ - Verify the Slyos server is accessible
451
+ - Ensure internet connection is active
452
+
453
+ ### "Cannot find module '@emilshirokikh/slyos-sdk'"
454
+ - Run `npm install` to install dependencies
455
+ - Check npm log: `npm list`
456
+
457
+ ### Placeholder API Key Warning
458
+ - Set the `SLYOS_API_KEY` environment variable with your actual key
459
+ - Or update `config.apiKey` in `app.mjs`
460
+
461
+ ## System Requirements
462
+
463
+ - Node.js 14+ (14.17.0 or higher recommended)
464
+ - npm 6+
465
+ - Internet connection for API access
466
+
467
+ ## License
468
+
469
+ MIT
470
+ README_EOF
471
+ print_success "README created"
472
+
473
+ # Set up environment with provided values
474
+ print_step "Configuring environment variables"
475
+ cat > .env << ENV_SETUP_EOF
476
+ SLYOS_API_KEY=${API_KEY}
477
+ SLYOS_MODEL=${MODEL}
478
+ SLYOS_SERVER=${SLYOS_SERVER}
479
+ ENV_SETUP_EOF
480
+ print_success "Environment configured"
481
+
482
+ # Final summary
483
+ echo ""
484
+ echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
485
+ echo -e "${BLUE}║${NC} ${GREEN}✓ Setup Complete!${NC} ${BLUE}║${NC}"
486
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
487
+ echo ""
488
+ echo -e "${CYAN}Project Details:${NC}"
489
+ echo " Location: ${YELLOW}$(pwd)${NC}"
490
+ echo " API Key: ${YELLOW}${API_KEY}${NC}"
491
+ echo " Model: ${YELLOW}${MODEL}${NC}"
492
+ echo ""
493
+ echo -e "${CYAN}Next Steps:${NC}"
494
+ echo " 1. Review the .env file and update your API key if needed"
495
+ echo " 2. Run the chatbot: ${YELLOW}npm start${NC}"
496
+ echo " 3. Type messages to chat with the AI"
497
+ echo " 4. Type 'exit' to quit"
498
+ echo ""
499
+ echo -e "${GREEN}Ready to chat! 🚀${NC}"
500
+ echo ""
501
+
502
+ # Tell user how to start (can't auto-run when piped because stdin is closed)
503
+ echo -e "${CYAN}To start chatting, run:${NC}"
504
+ echo ""
505
+ echo -e " ${YELLOW}cd ${PROJECT_NAME} && npm start${NC}"
506
+ echo ""
package/dist/index.d.ts CHANGED
@@ -31,12 +31,81 @@ interface ProgressEvent {
31
31
  detail?: any;
32
32
  }
33
33
  interface SlyEvent {
34
- type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error';
34
+ type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
35
35
  data?: any;
36
36
  timestamp: number;
37
37
  }
38
38
  type ProgressCallback = (event: ProgressEvent) => void;
39
39
  type EventCallback = (event: SlyEvent) => void;
40
+ interface OpenAIMessage {
41
+ role: 'system' | 'user' | 'assistant';
42
+ content: string;
43
+ }
44
+ interface OpenAIChatCompletionRequest {
45
+ messages: OpenAIMessage[];
46
+ temperature?: number;
47
+ top_p?: number;
48
+ max_tokens?: number;
49
+ frequency_penalty?: number;
50
+ presence_penalty?: number;
51
+ stop?: string | string[];
52
+ }
53
+ interface OpenAIChoice {
54
+ index: number;
55
+ message: OpenAIMessage;
56
+ finish_reason: string;
57
+ }
58
+ interface OpenAIUsage {
59
+ prompt_tokens: number;
60
+ completion_tokens: number;
61
+ total_tokens: number;
62
+ }
63
+ interface OpenAIChatCompletionResponse {
64
+ id: string;
65
+ object: 'chat.completion';
66
+ created: number;
67
+ model: string;
68
+ choices: OpenAIChoice[];
69
+ usage: OpenAIUsage;
70
+ }
71
+ interface BedrockTextGenerationConfig {
72
+ maxTokenCount?: number;
73
+ temperature?: number;
74
+ topP?: number;
75
+ topK?: number;
76
+ stopSequences?: string[];
77
+ }
78
+ interface BedrockInvokeRequest {
79
+ inputText: string;
80
+ textGenerationConfig?: BedrockTextGenerationConfig;
81
+ }
82
+ interface BedrockResult {
83
+ outputText: string;
84
+ tokenCount: number;
85
+ }
86
+ interface BedrockInvokeResponse {
87
+ results: BedrockResult[];
88
+ input_text_token_count?: number;
89
+ }
90
+ type FallbackProvider = 'openai' | 'bedrock';
91
+ interface FallbackConfig {
92
+ provider: FallbackProvider;
93
+ apiKey: string;
94
+ model: string;
95
+ region?: string;
96
+ }
97
+ interface SlyOSConfigWithFallback extends SlyOSConfig {
98
+ fallback?: FallbackConfig;
99
+ }
100
+ interface OpenAICompatibleClient {
101
+ chat: {
102
+ completions: {
103
+ create(request: OpenAIChatCompletionRequest & {
104
+ model: string;
105
+ }): Promise<OpenAIChatCompletionResponse>;
106
+ };
107
+ };
108
+ }
40
109
  declare class SlyOS {
41
110
  private apiKey;
42
111
  private apiUrl;
@@ -46,7 +115,8 @@ declare class SlyOS {
46
115
  private deviceProfile;
47
116
  private onProgress;
48
117
  private onEvent;
49
- constructor(config: SlyOSConfig);
118
+ private fallbackConfig;
119
+ constructor(config: SlyOSConfigWithFallback);
50
120
  private emitProgress;
51
121
  private emitEvent;
52
122
  analyzeDevice(): Promise<DeviceProfile>;
@@ -75,6 +145,19 @@ declare class SlyOS {
75
145
  }): Promise<void>;
76
146
  generate(modelId: string, prompt: string, options?: GenerateOptions): Promise<string>;
77
147
  transcribe(modelId: string, audioInput: any, options?: TranscribeOptions): Promise<string>;
148
+ chatCompletion(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse>;
149
+ bedrockInvoke(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse>;
150
+ private fallbackToOpenAI;
151
+ private fallbackToBedrock;
152
+ private fallbackToOpenAICloud;
153
+ private fallbackToBedrockCloud;
154
+ private invokeBedrockCloud;
155
+ private mapModelToOpenAI;
156
+ static openaiCompatible(config: {
157
+ apiKey: string;
158
+ apiUrl?: string;
159
+ fallback?: FallbackConfig;
160
+ }): OpenAICompatibleClient;
78
161
  }
79
162
  export default SlyOS;
80
- export type { SlyOSConfig, GenerateOptions, TranscribeOptions, DeviceProfile, ProgressEvent, SlyEvent, QuantizationLevel, ModelCategory };
163
+ export type { SlyOSConfig, SlyOSConfigWithFallback, GenerateOptions, TranscribeOptions, DeviceProfile, ProgressEvent, SlyEvent, QuantizationLevel, ModelCategory, OpenAIMessage, OpenAIChatCompletionRequest, OpenAIChatCompletionResponse, OpenAIChoice, OpenAIUsage, BedrockTextGenerationConfig, BedrockInvokeRequest, BedrockInvokeResponse, BedrockResult, FallbackConfig, FallbackProvider, OpenAICompatibleClient, };
package/dist/index.js CHANGED
@@ -153,6 +153,7 @@ class SlyOS {
153
153
  this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
154
154
  this.onProgress = config.onProgress || null;
155
155
  this.onEvent = config.onEvent || null;
156
+ this.fallbackConfig = config.fallback || null;
156
157
  }
157
158
  // ── Progress & Event Helpers ────────────────────────────────────
158
159
  emitProgress(stage, progress, message, detail) {
@@ -412,7 +413,12 @@ class SlyOS {
412
413
  top_p: options.topP || 0.9,
413
414
  do_sample: true,
414
415
  });
415
- const response = result[0].generated_text;
416
+ const rawOutput = result[0].generated_text;
417
+ // HuggingFace transformers returns the prompt + generated text concatenated.
418
+ // Strip the original prompt so we only return the NEW tokens.
419
+ const response = rawOutput.startsWith(prompt)
420
+ ? rawOutput.slice(prompt.length).trim()
421
+ : rawOutput.trim();
416
422
  const latency = Date.now() - startTime;
417
423
  const tokensGenerated = response.split(/\s+/).length;
418
424
  const tokensPerSec = (tokensGenerated / (latency / 1000)).toFixed(1);
@@ -500,5 +506,277 @@ class SlyOS {
500
506
  throw error;
501
507
  }
502
508
  }
509
+ // ── OpenAI Compatibility ────────────────────────────────────────────
510
+ async chatCompletion(modelId, request) {
511
+ try {
512
+ // Convert OpenAI message format to a prompt string
513
+ const prompt = request.messages
514
+ .map(msg => {
515
+ if (msg.role === 'system') {
516
+ return `System: ${msg.content}`;
517
+ }
518
+ else if (msg.role === 'user') {
519
+ return `User: ${msg.content}`;
520
+ }
521
+ else {
522
+ return `Assistant: ${msg.content}`;
523
+ }
524
+ })
525
+ .join('\n\n');
526
+ const response = await this.generate(modelId, prompt, {
527
+ temperature: request.temperature,
528
+ maxTokens: request.max_tokens,
529
+ topP: request.top_p,
530
+ });
531
+ // Estimate token counts (rough approximation: ~4 chars per token)
532
+ const promptTokens = Math.ceil(prompt.length / 4);
533
+ const completionTokens = Math.ceil(response.length / 4);
534
+ return {
535
+ id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
536
+ object: 'chat.completion',
537
+ created: Math.floor(Date.now() / 1000),
538
+ model: modelId,
539
+ choices: [
540
+ {
541
+ index: 0,
542
+ message: {
543
+ role: 'assistant',
544
+ content: response,
545
+ },
546
+ finish_reason: 'stop',
547
+ },
548
+ ],
549
+ usage: {
550
+ prompt_tokens: promptTokens,
551
+ completion_tokens: completionTokens,
552
+ total_tokens: promptTokens + completionTokens,
553
+ },
554
+ };
555
+ }
556
+ catch (error) {
557
+ // Fallback to cloud provider if configured
558
+ if (this.fallbackConfig?.provider === 'openai') {
559
+ return this.fallbackToOpenAI(modelId, request);
560
+ }
561
+ else if (this.fallbackConfig?.provider === 'bedrock') {
562
+ return this.fallbackToBedrock(modelId, request);
563
+ }
564
+ throw error;
565
+ }
566
+ }
567
+ // ── AWS Bedrock Compatibility ──────────────────────────────────────
568
+ async bedrockInvoke(modelId, request) {
569
+ try {
570
+ const response = await this.generate(modelId, request.inputText, {
571
+ temperature: request.textGenerationConfig?.temperature,
572
+ maxTokens: request.textGenerationConfig?.maxTokenCount,
573
+ topP: request.textGenerationConfig?.topP,
574
+ });
575
+ // Estimate token counts
576
+ const inputTokens = Math.ceil(request.inputText.length / 4);
577
+ const outputTokens = Math.ceil(response.length / 4);
578
+ return {
579
+ results: [
580
+ {
581
+ outputText: response,
582
+ tokenCount: outputTokens,
583
+ },
584
+ ],
585
+ input_text_token_count: inputTokens,
586
+ };
587
+ }
588
+ catch (error) {
589
+ // Fallback to cloud provider if configured
590
+ if (this.fallbackConfig?.provider === 'bedrock') {
591
+ return this.fallbackToBedrockCloud(modelId, request);
592
+ }
593
+ else if (this.fallbackConfig?.provider === 'openai') {
594
+ return this.fallbackToOpenAICloud(modelId, request);
595
+ }
596
+ throw error;
597
+ }
598
+ }
599
+ // ── Fallback: OpenAI Cloud ────────────────────────────────────────
600
+ async fallbackToOpenAI(modelId, request) {
601
+ if (!this.fallbackConfig) {
602
+ throw new Error('OpenAI fallback not configured');
603
+ }
604
+ const mappedModel = this.mapModelToOpenAI(modelId);
605
+ const payload = {
606
+ model: this.fallbackConfig.model || mappedModel,
607
+ messages: request.messages,
608
+ temperature: request.temperature,
609
+ max_tokens: request.max_tokens,
610
+ top_p: request.top_p,
611
+ frequency_penalty: request.frequency_penalty,
612
+ presence_penalty: request.presence_penalty,
613
+ stop: request.stop,
614
+ };
615
+ try {
616
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
617
+ headers: {
618
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
619
+ 'Content-Type': 'application/json',
620
+ },
621
+ });
622
+ this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
623
+ return response.data;
624
+ }
625
+ catch (error) {
626
+ this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
627
+ this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
628
+ throw error;
629
+ }
630
+ }
631
+ async fallbackToBedrock(modelId, request) {
632
+ if (!this.fallbackConfig) {
633
+ throw new Error('Bedrock fallback not configured');
634
+ }
635
+ // Convert OpenAI format to Bedrock's expected format (simplified)
636
+ const lastMessage = request.messages[request.messages.length - 1];
637
+ const inputText = lastMessage.content;
638
+ const bedrockResponse = await this.invokeBedrockCloud(inputText, {
639
+ temperature: request.temperature,
640
+ maxTokenCount: request.max_tokens,
641
+ topP: request.top_p,
642
+ });
643
+ // Convert Bedrock response back to OpenAI format
644
+ const promptTokens = Math.ceil(inputText.length / 4);
645
+ const completionTokens = bedrockResponse.results[0].tokenCount;
646
+ this.emitEvent('fallback_success', { provider: 'bedrock', originalModel: modelId, mappedModel: this.fallbackConfig.model });
647
+ return {
648
+ id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
649
+ object: 'chat.completion',
650
+ created: Math.floor(Date.now() / 1000),
651
+ model: modelId,
652
+ choices: [
653
+ {
654
+ index: 0,
655
+ message: {
656
+ role: 'assistant',
657
+ content: bedrockResponse.results[0].outputText,
658
+ },
659
+ finish_reason: 'stop',
660
+ },
661
+ ],
662
+ usage: {
663
+ prompt_tokens: promptTokens,
664
+ completion_tokens: completionTokens,
665
+ total_tokens: promptTokens + completionTokens,
666
+ },
667
+ };
668
+ }
669
+ async fallbackToOpenAICloud(modelId, request) {
670
+ if (!this.fallbackConfig) {
671
+ throw new Error('OpenAI fallback not configured');
672
+ }
673
+ const mappedModel = this.mapModelToOpenAI(modelId);
674
+ const payload = {
675
+ model: this.fallbackConfig.model || mappedModel,
676
+ messages: [{ role: 'user', content: request.inputText }],
677
+ temperature: request.textGenerationConfig?.temperature,
678
+ max_tokens: request.textGenerationConfig?.maxTokenCount,
679
+ top_p: request.textGenerationConfig?.topP,
680
+ };
681
+ try {
682
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
683
+ headers: {
684
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
685
+ 'Content-Type': 'application/json',
686
+ },
687
+ });
688
+ const outputText = response.data.choices[0].message.content;
689
+ const inputTokens = Math.ceil(request.inputText.length / 4);
690
+ const outputTokens = response.data.usage.completion_tokens;
691
+ this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
692
+ return {
693
+ results: [
694
+ {
695
+ outputText,
696
+ tokenCount: outputTokens,
697
+ },
698
+ ],
699
+ input_text_token_count: inputTokens,
700
+ };
701
+ }
702
+ catch (error) {
703
+ this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
704
+ this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
705
+ throw error;
706
+ }
707
+ }
708
+ async fallbackToBedrockCloud(modelId, request) {
709
+ if (!this.fallbackConfig) {
710
+ throw new Error('Bedrock fallback not configured');
711
+ }
712
+ try {
713
+ return await this.invokeBedrockCloud(request.inputText, request.textGenerationConfig);
714
+ }
715
+ catch (error) {
716
+ this.emitProgress('error', 0, `Bedrock fallback failed: ${error.message}`);
717
+ this.emitEvent('fallback_error', { provider: 'bedrock', error: error.message });
718
+ throw error;
719
+ }
720
+ }
721
+ async invokeBedrockCloud(inputText, config) {
722
+ if (!this.fallbackConfig) {
723
+ throw new Error('Bedrock fallback not configured');
724
+ }
725
+ const region = this.fallbackConfig.region || 'us-east-1';
726
+ const model = this.fallbackConfig.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
727
+ // Bedrock endpoint format: https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke
728
+ const endpoint = `https://bedrock-runtime.${region}.amazonaws.com/model/${model}/invoke`;
729
+ const payload = {
730
+ inputText,
731
+ textGenerationConfig: {
732
+ maxTokenCount: config?.maxTokenCount || 256,
733
+ temperature: config?.temperature || 0.7,
734
+ topP: config?.topP || 0.9,
735
+ topK: config?.topK,
736
+ stopSequences: config?.stopSequences,
737
+ },
738
+ };
739
+ try {
740
+ const response = await axios.post(endpoint, payload, {
741
+ headers: {
742
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
743
+ 'Content-Type': 'application/json',
744
+ 'X-Amz-Target': 'AmazonBedrockRuntime.InvokeModel',
745
+ },
746
+ });
747
+ this.emitEvent('fallback_success', { provider: 'bedrock', model });
748
+ return response.data;
749
+ }
750
+ catch (error) {
751
+ throw new Error(`Bedrock invocation failed: ${error.message}`);
752
+ }
753
+ }
754
+ mapModelToOpenAI(slyModelId) {
755
+ const modelMapping = {
756
+ 'quantum-1.7b': 'gpt-4o-mini',
757
+ 'quantum-3b': 'gpt-4o',
758
+ 'quantum-code-3b': 'gpt-4o',
759
+ 'quantum-8b': 'gpt-4-turbo',
760
+ };
761
+ return modelMapping[slyModelId] || 'gpt-4o-mini';
762
+ }
763
+ // ── Static OpenAI Compatible Factory ────────────────────────────────
764
+ static openaiCompatible(config) {
765
+ const instance = new SlyOS({
766
+ apiKey: config.apiKey,
767
+ apiUrl: config.apiUrl,
768
+ fallback: { ...config.fallback, provider: config.fallback?.provider || 'openai' },
769
+ });
770
+ return {
771
+ chat: {
772
+ completions: {
773
+ async create(request) {
774
+ const { model, ...chatRequest } = request;
775
+ return instance.chatCompletion(model, chatRequest);
776
+ },
777
+ },
778
+ },
779
+ };
780
+ }
503
781
  }
504
782
  export default SlyOS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emilshirokikh/slyos-sdk",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "SlyOS - On-Device AI SDK for Web and Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -55,7 +55,7 @@ interface ProgressEvent {
55
55
  }
56
56
 
57
57
  interface SlyEvent {
58
- type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error';
58
+ type: 'auth' | 'device_registered' | 'device_profiled' | 'model_download_start' | 'model_download_progress' | 'model_loaded' | 'inference_start' | 'inference_complete' | 'error' | 'fallback_success' | 'fallback_error';
59
59
  data?: any;
60
60
  timestamp: number;
61
61
  }
@@ -63,6 +63,94 @@ interface SlyEvent {
63
63
  type ProgressCallback = (event: ProgressEvent) => void;
64
64
  type EventCallback = (event: SlyEvent) => void;
65
65
 
66
+ // ─── OpenAI Compatibility Types ──────────────────────────────────────
67
+
68
+ interface OpenAIMessage {
69
+ role: 'system' | 'user' | 'assistant';
70
+ content: string;
71
+ }
72
+
73
+ interface OpenAIChatCompletionRequest {
74
+ messages: OpenAIMessage[];
75
+ temperature?: number;
76
+ top_p?: number;
77
+ max_tokens?: number;
78
+ frequency_penalty?: number;
79
+ presence_penalty?: number;
80
+ stop?: string | string[];
81
+ }
82
+
83
+ interface OpenAIChoice {
84
+ index: number;
85
+ message: OpenAIMessage;
86
+ finish_reason: string;
87
+ }
88
+
89
+ interface OpenAIUsage {
90
+ prompt_tokens: number;
91
+ completion_tokens: number;
92
+ total_tokens: number;
93
+ }
94
+
95
+ interface OpenAIChatCompletionResponse {
96
+ id: string;
97
+ object: 'chat.completion';
98
+ created: number;
99
+ model: string;
100
+ choices: OpenAIChoice[];
101
+ usage: OpenAIUsage;
102
+ }
103
+
104
+ // ─── AWS Bedrock Compatibility Types ─────────────────────────────────
105
+
106
+ interface BedrockTextGenerationConfig {
107
+ maxTokenCount?: number;
108
+ temperature?: number;
109
+ topP?: number;
110
+ topK?: number;
111
+ stopSequences?: string[];
112
+ }
113
+
114
+ interface BedrockInvokeRequest {
115
+ inputText: string;
116
+ textGenerationConfig?: BedrockTextGenerationConfig;
117
+ }
118
+
119
+ interface BedrockResult {
120
+ outputText: string;
121
+ tokenCount: number;
122
+ }
123
+
124
+ interface BedrockInvokeResponse {
125
+ results: BedrockResult[];
126
+ input_text_token_count?: number;
127
+ }
128
+
129
+ // ─── Fallback Configuration ─────────────────────────────────────────
130
+
131
+ type FallbackProvider = 'openai' | 'bedrock';
132
+
133
+ interface FallbackConfig {
134
+ provider: FallbackProvider;
135
+ apiKey: string;
136
+ model: string;
137
+ region?: string; // for Bedrock
138
+ }
139
+
140
+ interface SlyOSConfigWithFallback extends SlyOSConfig {
141
+ fallback?: FallbackConfig;
142
+ }
143
+
144
+ // ─── OpenAI Compatible Client ───────────────────────────────────────
145
+
146
+ interface OpenAICompatibleClient {
147
+ chat: {
148
+ completions: {
149
+ create(request: OpenAIChatCompletionRequest & { model: string }): Promise<OpenAIChatCompletionResponse>;
150
+ };
151
+ };
152
+ }
153
+
66
154
  // ─── Model Registry ─────────────────────────────────────────────────
67
155
 
68
156
  const modelMap: Record<string, ModelInfo> = {
@@ -218,13 +306,15 @@ class SlyOS {
218
306
  private deviceProfile: DeviceProfile | null = null;
219
307
  private onProgress: ProgressCallback | null;
220
308
  private onEvent: EventCallback | null;
309
+ private fallbackConfig: FallbackConfig | null;
221
310
 
222
- constructor(config: SlyOSConfig) {
311
+ constructor(config: SlyOSConfigWithFallback) {
223
312
  this.apiKey = config.apiKey;
224
313
  this.apiUrl = config.apiUrl || 'https://api.slyos.world';
225
314
  this.deviceId = `device-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
226
315
  this.onProgress = config.onProgress || null;
227
316
  this.onEvent = config.onEvent || null;
317
+ this.fallbackConfig = config.fallback || null;
228
318
  }
229
319
 
230
320
  // ── Progress & Event Helpers ────────────────────────────────────
@@ -525,7 +615,12 @@ class SlyOS {
525
615
  do_sample: true,
526
616
  });
527
617
 
528
- const response = result[0].generated_text;
618
+ const rawOutput = result[0].generated_text;
619
+ // HuggingFace transformers returns the prompt + generated text concatenated.
620
+ // Strip the original prompt so we only return the NEW tokens.
621
+ const response = rawOutput.startsWith(prompt)
622
+ ? rawOutput.slice(prompt.length).trim()
623
+ : rawOutput.trim();
529
624
  const latency = Date.now() - startTime;
530
625
  const tokensGenerated = response.split(/\s+/).length;
531
626
  const tokensPerSec = (tokensGenerated / (latency / 1000)).toFixed(1);
@@ -625,7 +720,329 @@ class SlyOS {
625
720
  throw error;
626
721
  }
627
722
  }
723
+
724
+ // ── OpenAI Compatibility ────────────────────────────────────────────
725
+
726
+ async chatCompletion(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
727
+ try {
728
+ // Convert OpenAI message format to a prompt string
729
+ const prompt = request.messages
730
+ .map(msg => {
731
+ if (msg.role === 'system') {
732
+ return `System: ${msg.content}`;
733
+ } else if (msg.role === 'user') {
734
+ return `User: ${msg.content}`;
735
+ } else {
736
+ return `Assistant: ${msg.content}`;
737
+ }
738
+ })
739
+ .join('\n\n');
740
+
741
+ const response = await this.generate(modelId, prompt, {
742
+ temperature: request.temperature,
743
+ maxTokens: request.max_tokens,
744
+ topP: request.top_p,
745
+ });
746
+
747
+ // Estimate token counts (rough approximation: ~4 chars per token)
748
+ const promptTokens = Math.ceil(prompt.length / 4);
749
+ const completionTokens = Math.ceil(response.length / 4);
750
+
751
+ return {
752
+ id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
753
+ object: 'chat.completion',
754
+ created: Math.floor(Date.now() / 1000),
755
+ model: modelId,
756
+ choices: [
757
+ {
758
+ index: 0,
759
+ message: {
760
+ role: 'assistant',
761
+ content: response,
762
+ },
763
+ finish_reason: 'stop',
764
+ },
765
+ ],
766
+ usage: {
767
+ prompt_tokens: promptTokens,
768
+ completion_tokens: completionTokens,
769
+ total_tokens: promptTokens + completionTokens,
770
+ },
771
+ };
772
+ } catch (error: any) {
773
+ // Fallback to cloud provider if configured
774
+ if (this.fallbackConfig?.provider === 'openai') {
775
+ return this.fallbackToOpenAI(modelId, request);
776
+ } else if (this.fallbackConfig?.provider === 'bedrock') {
777
+ return this.fallbackToBedrock(modelId, request);
778
+ }
779
+ throw error;
780
+ }
781
+ }
782
+
783
+ // ── AWS Bedrock Compatibility ──────────────────────────────────────
784
+
785
+ async bedrockInvoke(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
786
+ try {
787
+ const response = await this.generate(modelId, request.inputText, {
788
+ temperature: request.textGenerationConfig?.temperature,
789
+ maxTokens: request.textGenerationConfig?.maxTokenCount,
790
+ topP: request.textGenerationConfig?.topP,
791
+ });
792
+
793
+ // Estimate token counts
794
+ const inputTokens = Math.ceil(request.inputText.length / 4);
795
+ const outputTokens = Math.ceil(response.length / 4);
796
+
797
+ return {
798
+ results: [
799
+ {
800
+ outputText: response,
801
+ tokenCount: outputTokens,
802
+ },
803
+ ],
804
+ input_text_token_count: inputTokens,
805
+ };
806
+ } catch (error: any) {
807
+ // Fallback to cloud provider if configured
808
+ if (this.fallbackConfig?.provider === 'bedrock') {
809
+ return this.fallbackToBedrockCloud(modelId, request);
810
+ } else if (this.fallbackConfig?.provider === 'openai') {
811
+ return this.fallbackToOpenAICloud(modelId, request);
812
+ }
813
+ throw error;
814
+ }
815
+ }
816
+
817
+ // ── Fallback: OpenAI Cloud ────────────────────────────────────────
818
+
819
+ private async fallbackToOpenAI(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
820
+ if (!this.fallbackConfig) {
821
+ throw new Error('OpenAI fallback not configured');
822
+ }
823
+
824
+ const mappedModel = this.mapModelToOpenAI(modelId);
825
+ const payload = {
826
+ model: this.fallbackConfig.model || mappedModel,
827
+ messages: request.messages,
828
+ temperature: request.temperature,
829
+ max_tokens: request.max_tokens,
830
+ top_p: request.top_p,
831
+ frequency_penalty: request.frequency_penalty,
832
+ presence_penalty: request.presence_penalty,
833
+ stop: request.stop,
834
+ };
835
+
836
+ try {
837
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
838
+ headers: {
839
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
840
+ 'Content-Type': 'application/json',
841
+ },
842
+ });
843
+
844
+ this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
845
+ return response.data;
846
+ } catch (error: any) {
847
+ this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
848
+ this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
849
+ throw error;
850
+ }
851
+ }
852
+
853
+ private async fallbackToBedrock(modelId: string, request: OpenAIChatCompletionRequest): Promise<OpenAIChatCompletionResponse> {
854
+ if (!this.fallbackConfig) {
855
+ throw new Error('Bedrock fallback not configured');
856
+ }
857
+
858
+ // Convert OpenAI format to Bedrock's expected format (simplified)
859
+ const lastMessage = request.messages[request.messages.length - 1];
860
+ const inputText = lastMessage.content;
861
+
862
+ const bedrockResponse = await this.invokeBedrockCloud(inputText, {
863
+ temperature: request.temperature,
864
+ maxTokenCount: request.max_tokens,
865
+ topP: request.top_p,
866
+ });
867
+
868
+ // Convert Bedrock response back to OpenAI format
869
+ const promptTokens = Math.ceil(inputText.length / 4);
870
+ const completionTokens = bedrockResponse.results[0].tokenCount;
871
+
872
+ this.emitEvent('fallback_success', { provider: 'bedrock', originalModel: modelId, mappedModel: this.fallbackConfig.model });
873
+
874
+ return {
875
+ id: `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
876
+ object: 'chat.completion',
877
+ created: Math.floor(Date.now() / 1000),
878
+ model: modelId,
879
+ choices: [
880
+ {
881
+ index: 0,
882
+ message: {
883
+ role: 'assistant',
884
+ content: bedrockResponse.results[0].outputText,
885
+ },
886
+ finish_reason: 'stop',
887
+ },
888
+ ],
889
+ usage: {
890
+ prompt_tokens: promptTokens,
891
+ completion_tokens: completionTokens,
892
+ total_tokens: promptTokens + completionTokens,
893
+ },
894
+ };
895
+ }
896
+
897
+ private async fallbackToOpenAICloud(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
898
+ if (!this.fallbackConfig) {
899
+ throw new Error('OpenAI fallback not configured');
900
+ }
901
+
902
+ const mappedModel = this.mapModelToOpenAI(modelId);
903
+ const payload = {
904
+ model: this.fallbackConfig.model || mappedModel,
905
+ messages: [{ role: 'user', content: request.inputText }],
906
+ temperature: request.textGenerationConfig?.temperature,
907
+ max_tokens: request.textGenerationConfig?.maxTokenCount,
908
+ top_p: request.textGenerationConfig?.topP,
909
+ };
910
+
911
+ try {
912
+ const response = await axios.post('https://api.openai.com/v1/chat/completions', payload, {
913
+ headers: {
914
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
915
+ 'Content-Type': 'application/json',
916
+ },
917
+ });
918
+
919
+ const outputText = response.data.choices[0].message.content;
920
+ const inputTokens = Math.ceil(request.inputText.length / 4);
921
+ const outputTokens = response.data.usage.completion_tokens;
922
+
923
+ this.emitEvent('fallback_success', { provider: 'openai', originalModel: modelId, mappedModel: this.fallbackConfig.model });
924
+
925
+ return {
926
+ results: [
927
+ {
928
+ outputText,
929
+ tokenCount: outputTokens,
930
+ },
931
+ ],
932
+ input_text_token_count: inputTokens,
933
+ };
934
+ } catch (error: any) {
935
+ this.emitProgress('error', 0, `OpenAI fallback failed: ${error.message}`);
936
+ this.emitEvent('fallback_error', { provider: 'openai', error: error.message });
937
+ throw error;
938
+ }
939
+ }
940
+
941
+ private async fallbackToBedrockCloud(modelId: string, request: BedrockInvokeRequest): Promise<BedrockInvokeResponse> {
942
+ if (!this.fallbackConfig) {
943
+ throw new Error('Bedrock fallback not configured');
944
+ }
945
+
946
+ try {
947
+ return await this.invokeBedrockCloud(request.inputText, request.textGenerationConfig);
948
+ } catch (error: any) {
949
+ this.emitProgress('error', 0, `Bedrock fallback failed: ${error.message}`);
950
+ this.emitEvent('fallback_error', { provider: 'bedrock', error: error.message });
951
+ throw error;
952
+ }
953
+ }
954
+
955
+ private async invokeBedrockCloud(inputText: string, config?: BedrockTextGenerationConfig): Promise<BedrockInvokeResponse> {
956
+ if (!this.fallbackConfig) {
957
+ throw new Error('Bedrock fallback not configured');
958
+ }
959
+
960
+ const region = this.fallbackConfig.region || 'us-east-1';
961
+ const model = this.fallbackConfig.model || 'anthropic.claude-3-sonnet-20240229-v1:0';
962
+
963
+ // Bedrock endpoint format: https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invoke
964
+ const endpoint = `https://bedrock-runtime.${region}.amazonaws.com/model/${model}/invoke`;
965
+
966
+ const payload = {
967
+ inputText,
968
+ textGenerationConfig: {
969
+ maxTokenCount: config?.maxTokenCount || 256,
970
+ temperature: config?.temperature || 0.7,
971
+ topP: config?.topP || 0.9,
972
+ topK: config?.topK,
973
+ stopSequences: config?.stopSequences,
974
+ },
975
+ };
976
+
977
+ try {
978
+ const response = await axios.post(endpoint, payload, {
979
+ headers: {
980
+ Authorization: `Bearer ${this.fallbackConfig.apiKey}`,
981
+ 'Content-Type': 'application/json',
982
+ 'X-Amz-Target': 'AmazonBedrockRuntime.InvokeModel',
983
+ },
984
+ });
985
+
986
+ this.emitEvent('fallback_success', { provider: 'bedrock', model });
987
+ return response.data;
988
+ } catch (error: any) {
989
+ throw new Error(`Bedrock invocation failed: ${error.message}`);
990
+ }
991
+ }
992
+
993
+ private mapModelToOpenAI(slyModelId: string): string {
994
+ const modelMapping: Record<string, string> = {
995
+ 'quantum-1.7b': 'gpt-4o-mini',
996
+ 'quantum-3b': 'gpt-4o',
997
+ 'quantum-code-3b': 'gpt-4o',
998
+ 'quantum-8b': 'gpt-4-turbo',
999
+ };
1000
+ return modelMapping[slyModelId] || 'gpt-4o-mini';
1001
+ }
1002
+
1003
+ // ── Static OpenAI Compatible Factory ────────────────────────────────
1004
+
1005
+ static openaiCompatible(config: { apiKey: string; apiUrl?: string; fallback?: FallbackConfig }): OpenAICompatibleClient {
1006
+ const instance = new SlyOS({
1007
+ apiKey: config.apiKey,
1008
+ apiUrl: config.apiUrl,
1009
+ fallback: { ...config.fallback, provider: config.fallback?.provider || 'openai' } as FallbackConfig,
1010
+ });
1011
+
1012
+ return {
1013
+ chat: {
1014
+ completions: {
1015
+ async create(request: OpenAIChatCompletionRequest & { model: string }): Promise<OpenAIChatCompletionResponse> {
1016
+ const { model, ...chatRequest } = request;
1017
+ return instance.chatCompletion(model, chatRequest);
1018
+ },
1019
+ },
1020
+ },
1021
+ };
1022
+ }
628
1023
  }
629
1024
 
630
1025
  export default SlyOS;
631
- export type { SlyOSConfig, GenerateOptions, TranscribeOptions, DeviceProfile, ProgressEvent, SlyEvent, QuantizationLevel, ModelCategory };
1026
+ export type {
1027
+ SlyOSConfig,
1028
+ SlyOSConfigWithFallback,
1029
+ GenerateOptions,
1030
+ TranscribeOptions,
1031
+ DeviceProfile,
1032
+ ProgressEvent,
1033
+ SlyEvent,
1034
+ QuantizationLevel,
1035
+ ModelCategory,
1036
+ OpenAIMessage,
1037
+ OpenAIChatCompletionRequest,
1038
+ OpenAIChatCompletionResponse,
1039
+ OpenAIChoice,
1040
+ OpenAIUsage,
1041
+ BedrockTextGenerationConfig,
1042
+ BedrockInvokeRequest,
1043
+ BedrockInvokeResponse,
1044
+ BedrockResult,
1045
+ FallbackConfig,
1046
+ FallbackProvider,
1047
+ OpenAICompatibleClient,
1048
+ };