feat(ai): add hello world proxy client
This commit is contained in:
parent
d4dab69573
commit
dc09639cb1
114
apis/libaihelloworld.lua
Normal file
114
apis/libaihelloworld.lua
Normal file
@ -0,0 +1,114 @@
|
||||
local _VERSION = '0.1.0';
|
||||
|
||||
local DEFAULT_PROMPT = 'reply with exactly: pong';
|
||||
|
||||
local function trimTrailingSlash(value)
|
||||
return (value:gsub('/+$', ''));
|
||||
end
|
||||
|
||||
local function readAllAndClose(response)
|
||||
local body = response.readAll();
|
||||
response.close();
|
||||
return body;
|
||||
end
|
||||
|
||||
local function statusCode(response)
|
||||
if response.getResponseCode then
|
||||
return response.getResponseCode();
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
|
||||
local function mapStatusError(code)
|
||||
if code == 401 then return 'token invalide'; end
|
||||
if code == 413 then return 'prompt trop long'; end
|
||||
if code == 502 then return 'agent indisponible'; end
|
||||
if code == 504 then return 'agent trop lent'; end
|
||||
if code then return 'erreur proxy: HTTP ' .. tostring(code); end
|
||||
return 'erreur proxy inconnue';
|
||||
end
|
||||
|
||||
local function createAiHelloWorld(opts)
|
||||
opts = opts or {};
|
||||
|
||||
local httpLib = opts.http or http;
|
||||
local settingsLib = opts.settings or settings;
|
||||
local prompt = opts.prompt or DEFAULT_PROMPT;
|
||||
|
||||
local api = {};
|
||||
|
||||
function api.version()
|
||||
return _VERSION;
|
||||
end
|
||||
|
||||
function api.clearSession()
|
||||
settingsLib.unset('opencc.session_id');
|
||||
if settingsLib.save then settingsLib.save(); end
|
||||
end
|
||||
|
||||
function api.askHello(options)
|
||||
options = options or {};
|
||||
|
||||
local proxyUrl = options.proxyUrl or settingsLib.get('opencc.proxy_url');
|
||||
if not proxyUrl or proxyUrl == '' then
|
||||
return false, 'missing opencc.proxy_url; run: settings set opencc.proxy_url <url>';
|
||||
end
|
||||
|
||||
local token = options.proxyToken or settingsLib.get('opencc.proxy_token');
|
||||
if not token or token == '' then
|
||||
return false, 'missing opencc.proxy_token; run: settings set opencc.proxy_token <token>';
|
||||
end
|
||||
|
||||
local sessionId = options.sessionId;
|
||||
if sessionId == nil then
|
||||
sessionId = settingsLib.get('opencc.session_id');
|
||||
end
|
||||
|
||||
local body = { prompt = prompt };
|
||||
if sessionId and sessionId ~= '' then
|
||||
body.sessionId = sessionId;
|
||||
end
|
||||
|
||||
local response = httpLib.post(
|
||||
trimTrailingSlash(proxyUrl) .. '/ask',
|
||||
textutils.serializeJSON(body),
|
||||
{
|
||||
['Authorization'] = 'Bearer ' .. token,
|
||||
['Content-Type'] = 'application/json',
|
||||
}
|
||||
);
|
||||
|
||||
if not response then
|
||||
return false, 'proxy injoignable. Verifie opencc.proxy_url et http.rules';
|
||||
end
|
||||
|
||||
local code = statusCode(response);
|
||||
local responseBody = readAllAndClose(response);
|
||||
if code and code ~= 200 then
|
||||
return false, mapStatusError(code);
|
||||
end
|
||||
|
||||
local decoded = textutils.unserializeJSON(responseBody or '');
|
||||
if type(decoded) ~= 'table' then
|
||||
return false, 'reponse proxy invalide';
|
||||
end
|
||||
if type(decoded.reply) ~= 'string' then
|
||||
return false, 'reponse proxy sans reply';
|
||||
end
|
||||
|
||||
if type(decoded.sessionId) == 'string' and decoded.sessionId ~= '' then
|
||||
settingsLib.set('opencc.session_id', decoded.sessionId);
|
||||
if settingsLib.save then settingsLib.save(); end
|
||||
end
|
||||
|
||||
return true, {
|
||||
reply = decoded.reply,
|
||||
sessionId = decoded.sessionId,
|
||||
truncated = decoded.truncated == true,
|
||||
};
|
||||
end
|
||||
|
||||
return api;
|
||||
end
|
||||
|
||||
return createAiHelloWorld;
|
||||
46
programs/ai-helloworld.lua
Normal file
46
programs/ai-helloworld.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local _VERSION = '0.1.0';
|
||||
|
||||
local createAiHelloWorld = require('/apis/libaihelloworld');
|
||||
|
||||
local args = table.pack(...);
|
||||
local command = args[1];
|
||||
|
||||
local function printUsage()
|
||||
print('ai-helloworld usage:');
|
||||
print();
|
||||
print(' ai-helloworld');
|
||||
print(' ai-helloworld --new');
|
||||
print(' ai-helloworld --version');
|
||||
print(' ai-helloworld --help');
|
||||
print();
|
||||
print('settings required:');
|
||||
print(' opencc.proxy_url');
|
||||
print(' opencc.proxy_token');
|
||||
end
|
||||
|
||||
if command == '--version' or command == '-version' or command == 'version' then
|
||||
print('ai-helloworld v' .. _VERSION);
|
||||
return;
|
||||
end
|
||||
|
||||
if command == '--help' or command == '-help' or command == 'help' then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
|
||||
local ai = createAiHelloWorld();
|
||||
|
||||
if command == '--new' then
|
||||
ai.clearSession();
|
||||
elseif command ~= nil and command ~= '' then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
|
||||
local ok, result = ai.askHello();
|
||||
if not ok then
|
||||
print(result);
|
||||
return;
|
||||
end
|
||||
|
||||
print(result.reply);
|
||||
168
tests/ai-helloworld.lua
Normal file
168
tests/ai-helloworld.lua
Normal file
@ -0,0 +1,168 @@
|
||||
local createLibTest = require('/apis/libtest');
|
||||
local createAiHelloWorld = require('/apis/libaihelloworld');
|
||||
|
||||
local testlib = createLibTest({ ... });
|
||||
|
||||
local function fakeSettings(initial)
|
||||
local values = initial or {};
|
||||
local saveCount = 0;
|
||||
|
||||
return {
|
||||
get = function(key)
|
||||
return values[key];
|
||||
end,
|
||||
set = function(key, value)
|
||||
values[key] = value;
|
||||
end,
|
||||
unset = function(key)
|
||||
values[key] = nil;
|
||||
end,
|
||||
save = function()
|
||||
saveCount = saveCount + 1;
|
||||
end,
|
||||
values = values,
|
||||
saveCount = function()
|
||||
return saveCount;
|
||||
end,
|
||||
};
|
||||
end
|
||||
|
||||
local function response(code, body)
|
||||
return {
|
||||
getResponseCode = function()
|
||||
return code;
|
||||
end,
|
||||
readAll = function()
|
||||
return body;
|
||||
end,
|
||||
close = function() end,
|
||||
};
|
||||
end
|
||||
|
||||
local function fakeHttp(result)
|
||||
local calls = {};
|
||||
return {
|
||||
post = function(url, body, headers)
|
||||
calls[#calls + 1] = { url = url, body = body, headers = headers };
|
||||
if type(result) == 'function' then
|
||||
return result(url, body, headers);
|
||||
end
|
||||
return result;
|
||||
end,
|
||||
calls = calls,
|
||||
};
|
||||
end
|
||||
|
||||
testlib.test('askHello posts ping prompt and saves session id', function()
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.proxy_url'] = 'https://proxy.example/',
|
||||
['opencc.proxy_token'] = 'secret',
|
||||
});
|
||||
local httpStub = fakeHttp(response(200, textutils.serializeJSON({
|
||||
sessionId = 'ses_123',
|
||||
reply = 'pong',
|
||||
truncated = false,
|
||||
})));
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, result = ai.askHello();
|
||||
|
||||
testlib.assertTrue(ok, tostring(result));
|
||||
testlib.assertEquals(result.reply, 'pong');
|
||||
testlib.assertEquals(result.sessionId, 'ses_123');
|
||||
testlib.assertEquals(settingsStub.values['opencc.session_id'], 'ses_123');
|
||||
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||
|
||||
testlib.assertEquals(#httpStub.calls, 1);
|
||||
local call = httpStub.calls[1];
|
||||
testlib.assertEquals(call.url, 'https://proxy.example/ask');
|
||||
testlib.assertEquals(call.headers['Authorization'], 'Bearer secret');
|
||||
testlib.assertEquals(call.headers['Content-Type'], 'application/json');
|
||||
|
||||
local request = textutils.unserializeJSON(call.body);
|
||||
testlib.assertEquals(request.prompt, 'reply with exactly: pong');
|
||||
testlib.assertEquals(request.sessionId, nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello includes existing session id', function()
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.proxy_url'] = 'https://proxy.example',
|
||||
['opencc.proxy_token'] = 'secret',
|
||||
['opencc.session_id'] = 'ses_old',
|
||||
});
|
||||
local httpStub = fakeHttp(response(200, textutils.serializeJSON({
|
||||
sessionId = 'ses_old',
|
||||
reply = 'pong',
|
||||
})));
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok = ai.askHello();
|
||||
|
||||
testlib.assertTrue(ok);
|
||||
local request = textutils.unserializeJSON(httpStub.calls[1].body);
|
||||
testlib.assertEquals(request.sessionId, 'ses_old');
|
||||
end);
|
||||
|
||||
testlib.test('askHello rejects missing proxy url', function()
|
||||
local settingsStub = fakeSettings({ ['opencc.proxy_token'] = 'secret' });
|
||||
local httpStub = fakeHttp(response(200, '{}'));
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'opencc.proxy_url', 1, true));
|
||||
testlib.assertEquals(#httpStub.calls, 0);
|
||||
end);
|
||||
|
||||
testlib.test('askHello rejects missing proxy token', function()
|
||||
local settingsStub = fakeSettings({ ['opencc.proxy_url'] = 'https://proxy.example' });
|
||||
local httpStub = fakeHttp(response(200, '{}'));
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'opencc.proxy_token', 1, true));
|
||||
testlib.assertEquals(#httpStub.calls, 0);
|
||||
end);
|
||||
|
||||
testlib.test('askHello maps proxy unreachable', function()
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.proxy_url'] = 'https://proxy.example',
|
||||
['opencc.proxy_token'] = 'secret',
|
||||
});
|
||||
local httpStub = fakeHttp(nil);
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'proxy injoignable', 1, true));
|
||||
end);
|
||||
|
||||
testlib.test('askHello maps 401 response', function()
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.proxy_url'] = 'https://proxy.example',
|
||||
['opencc.proxy_token'] = 'secret',
|
||||
});
|
||||
local httpStub = fakeHttp(response(401, textutils.serializeJSON({ error = 'unauthorized' })));
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertEquals(err, 'token invalide');
|
||||
end);
|
||||
|
||||
testlib.test('clearSession unsets persisted session id', function()
|
||||
local settingsStub = fakeSettings({ ['opencc.session_id'] = 'ses_old' });
|
||||
local ai = createAiHelloWorld({ http = fakeHttp(nil), settings = settingsStub });
|
||||
|
||||
ai.clearSession();
|
||||
|
||||
testlib.assertEquals(settingsStub.values['opencc.session_id'], nil);
|
||||
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||
end);
|
||||
|
||||
testlib.run();
|
||||
Loading…
Reference in New Issue
Block a user