mirror of
https://github.com/Pavelevich/claw-alexa.git
synced 2026-03-16 01:16:15 +01:00
Initial commit: OpenClaw Alexa Skill
This commit is contained in:
245
lambda/index.js
Normal file
245
lambda/index.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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?'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
9
lambda/package.json
Normal file
9
lambda/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "openclaw-alexa-lambda",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-dynamodb": "^3.400.0",
|
||||
"@aws-sdk/lib-dynamodb": "^3.400.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user