Files
claw-alexa/lambda/index.js
2026-01-30 21:14:19 +01:00

246 lines
8.3 KiB
JavaScript

/**
* Alexa Skill Lambda - OpenClaw/Grok Integration
* Connects Alexa to your personal AI gateway
*/
const https = require('https');
const http = require('http');
// Gateway configuration - set via environment variables or Account Linking
const DEFAULT_GATEWAY_URL = process.env.OPENCLAW_GATEWAY_URL || '';
const DEFAULT_GATEWAY_PASSWORD = process.env.OPENCLAW_GATEWAY_PASSWORD || '';
// Fallback to Grok API
const XAI_API_KEY = process.env.XAI_API_KEY || '';
// Call OpenClaw gateway using OpenAI-compatible endpoint
async function callGateway(message) {
return new Promise((resolve, reject) => {
const url = new URL(DEFAULT_GATEWAY_URL);
// OpenClaw uses OpenAI-compatible chat completions endpoint
const postData = JSON.stringify({
model: 'openclaw',
messages: [
{
role: 'system',
content: 'You are Smart Claw, a helpful AI assistant connected via Alexa. Keep responses concise and suitable for voice output (under 150 words).'
},
{
role: 'user',
content: message
}
],
stream: false
});
const options = {
hostname: url.hostname,
port: 443,
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Authorization': `Bearer ${DEFAULT_GATEWAY_PASSWORD}`
},
timeout: 25000
};
console.log('Calling OpenClaw gateway:', DEFAULT_GATEWAY_URL + '/v1/chat/completions');
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
console.log('Gateway response status:', res.statusCode);
console.log('Gateway response data:', data.substring(0, 500));
// If gateway returns HTML or error, fall back to Grok
if (res.statusCode !== 200 || data.includes('<!DOCTYPE') || data.includes('<html')) {
reject(new Error(`Gateway returned status ${res.statusCode}`));
return;
}
try {
const json = JSON.parse(data);
// OpenAI-compatible response format
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else if (json.error) {
reject(new Error(json.error.message || 'Gateway error'));
} else {
reject(new Error('Unexpected gateway response format'));
}
} catch (e) {
reject(new Error('Could not parse gateway response: ' + e.message));
}
});
});
req.on('error', (e) => {
console.error('Gateway request error:', e.message);
reject(e);
});
req.on('timeout', () => {
console.error('Gateway request timeout');
req.destroy();
reject(new Error('Gateway timeout'));
});
req.write(postData);
req.end();
});
}
// Call Grok API directly (fallback)
async function callGrok(message) {
return new Promise((resolve, reject) => {
const postData = JSON.stringify({
model: 'grok-3-fast',
messages: [
{
role: 'system',
content: 'You are a helpful AI assistant called Smart Claw. Keep responses concise and suitable for voice output (under 150 words).'
},
{
role: 'user',
content: message
}
],
max_tokens: 400
});
const options = {
hostname: 'api.x.ai',
port: 443,
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Authorization': `Bearer ${XAI_API_KEY}`
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else {
resolve('Sorry, I could not process that request.');
}
} catch (e) {
resolve('Sorry, I encountered an error.');
}
});
});
req.on('error', reject);
req.write(postData);
req.end();
});
}
// Main handler
exports.handler = async (event) => {
console.log('Alexa Request:', JSON.stringify(event, null, 2));
const requestType = event.request.type;
// Launch request
if (requestType === 'LaunchRequest') {
return buildResponse(
'Welcome to Smart Claw, connected to your OpenClaw gateway. What would you like to know?',
false
);
}
// Intent request
if (requestType === 'IntentRequest') {
const intentName = event.request.intent.name;
if (intentName === 'AMAZON.HelpIntent') {
return buildResponse(
'I am connected to your OpenClaw AI gateway. You can ask me anything. Try saying: scan my network, or tell me a joke.',
false
);
}
if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
return buildResponse('Goodbye!', true);
}
if (intentName === 'AMAZON.FallbackIntent') {
return buildResponse('I did not catch that. Please try again.', false);
}
// Handle ChatIntent - main conversation
if (intentName === 'ChatIntent') {
const userMessage = event.request.intent.slots?.query?.value || 'Hello';
console.log('User message:', userMessage);
try {
let response;
// Try gateway first
try {
console.log('Attempting gateway call...');
response = await callGateway(userMessage);
console.log('Gateway success:', response?.substring(0, 100));
} catch (gatewayError) {
console.log('Gateway failed, using Grok:', gatewayError.message);
// Fallback to Grok
if (XAI_API_KEY) {
response = await callGrok(userMessage);
} else {
response = 'Sorry, I could not connect to the gateway. Please check if OpenClaw is running.';
}
}
// Clean and truncate response
let speechResponse = String(response).trim();
if (speechResponse.length > 6000) {
speechResponse = speechResponse.substring(0, 5900) + '... That is all for now.';
}
return buildResponse(speechResponse, false);
} catch (error) {
console.error('Handler error:', error);
return buildResponse('Sorry, I encountered an error. Please try again.', false);
}
}
}
// Session ended
if (requestType === 'SessionEndedRequest') {
return { version: '1.0', response: {} };
}
return buildResponse('I did not understand that. Please try again.', false);
};
function buildResponse(speechText, shouldEndSession) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'PlainText',
text: speechText
},
shouldEndSession: shouldEndSession,
reprompt: shouldEndSession ? undefined : {
outputSpeech: {
type: 'PlainText',
text: 'What else would you like to know?'
}
}
}
};
}