feat(ai): generalize opencode client
This commit is contained in:
parent
3d26b67003
commit
65d8a927b8
@ -1,6 +1,6 @@
|
||||
local _VERSION = '0.2.2';
|
||||
local _VERSION = '0.3.0';
|
||||
|
||||
local DEFAULT_PROMPT = 'reply with exactly: pong';
|
||||
local PING_PROMPT = 'reply with exactly: pong';
|
||||
|
||||
local B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
@ -27,6 +27,10 @@ local function trimTrailingSlash(s)
|
||||
return (s:gsub('/+$', ''));
|
||||
end
|
||||
|
||||
local function isBlank(s)
|
||||
return type(s) ~= 'string' or string.match(s, '^%s*$') ~= nil;
|
||||
end
|
||||
|
||||
local function readAllAndClose(response)
|
||||
local body = response.readAll();
|
||||
response.close();
|
||||
@ -50,12 +54,11 @@ local function extractTextParts(parts)
|
||||
return table.concat(texts, '');
|
||||
end
|
||||
|
||||
local function createAiHelloWorld(opts)
|
||||
local function createAi(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 = {};
|
||||
|
||||
@ -133,8 +136,12 @@ local function createAiHelloWorld(opts)
|
||||
return true, decoded;
|
||||
end
|
||||
|
||||
function api.askHello(options)
|
||||
function api.ask(prompt, options)
|
||||
options = options or {};
|
||||
if isBlank(prompt) then
|
||||
return false, 'missing prompt; usage: ai <prompt>';
|
||||
end
|
||||
|
||||
local cfg, err = resolveConfig(options);
|
||||
if not cfg then return false, err; end
|
||||
|
||||
@ -144,7 +151,7 @@ local function createAiHelloWorld(opts)
|
||||
end
|
||||
|
||||
if not sessionId or sessionId == '' then
|
||||
local body, code = doPost(cfg, '/session', { title = 'cc-helloworld' });
|
||||
local body, code = doPost(cfg, '/session', { title = 'cc-ai' });
|
||||
if not body then return false, code; end
|
||||
if code and code ~= 200 then
|
||||
return false, 'impossible de creer une session: HTTP ' .. tostring(code);
|
||||
@ -165,7 +172,7 @@ local function createAiHelloWorld(opts)
|
||||
if code == 404 then
|
||||
settingsLib.unset('opencc.session_id');
|
||||
if settingsLib.save then settingsLib.save(); end
|
||||
return false, 'session introuvable; lance: ai-helloworld --new';
|
||||
return false, 'session introuvable; lance: ai new <prompt>';
|
||||
end
|
||||
if code and code ~= 200 then
|
||||
return false, 'erreur message: HTTP ' .. tostring(code);
|
||||
@ -184,7 +191,11 @@ local function createAiHelloWorld(opts)
|
||||
return true, { reply = reply, sessionId = sessionId };
|
||||
end
|
||||
|
||||
function api.ping(options)
|
||||
return api.ask(PING_PROMPT, options);
|
||||
end
|
||||
|
||||
return api;
|
||||
end
|
||||
|
||||
return createAiHelloWorld;
|
||||
return createAi;
|
||||
@ -8,6 +8,8 @@ Start here when looking up ComputerCraft-related APIs, CraftOS-PC behavior, peri
|
||||
- [`craftos_pc_glossary.md`](craftos_pc_glossary.md) - CraftOS-PC emulator setup, CLI flags, mounting, peripheral emulation, troubleshooting, and related references.
|
||||
- [`advanced_peripherals_glossary.md`](advanced_peripherals_glossary.md) - Advanced Peripherals 0.7 guides, peripherals, turtles, integrations, and changelog pages.
|
||||
- [`create_cc_tweaked_glossary.md`](create_cc_tweaked_glossary.md) - Create CC:Tweaked integration pages.
|
||||
- [`opencode_server_guide.md`](opencode_server_guide.md) - Running `opencode serve` for the TrapOS `ai` client.
|
||||
- [`opencode_api.md`](opencode_api.md) - Minimal opencode HTTP API reference used by TrapOS.
|
||||
- [`adrs/`](adrs/) - Lightweight Architecture Decision Records for this repository.
|
||||
|
||||
## Notes
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# opencode serve — HTTP API reference
|
||||
|
||||
Minimal reference for the endpoints used by `libaihelloworld.lua`. Full spec served at `GET /doc` when the server is running.
|
||||
Minimal reference for the endpoints used by `libai.lua`. Full spec served at `GET /doc` when the server is running.
|
||||
|
||||
## Running the server
|
||||
|
||||
@ -133,7 +133,7 @@ opencode attach http://127.0.0.1:4096
|
||||
opencode attach http://127.0.0.1:4096 --session ses_abc123
|
||||
```
|
||||
|
||||
To send messages to the session currently open in the TUI, set `opencc.session_id` in CC to the session ID shown in the TUI, then run `ai-helloworld`:
|
||||
To send messages to the session currently open in the TUI, set `opencc.session_id` in CC to the session ID shown in the TUI, then run `ai`:
|
||||
|
||||
```sh
|
||||
set opencc.session_id ses_abc123
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# opencode server guide
|
||||
|
||||
How to run `opencode serve` and test `ai-helloworld` from ComputerCraft directly — no proxy.
|
||||
How to run `opencode serve` and use `ai` from ComputerCraft directly, no proxy.
|
||||
|
||||
See [`opencode_api.md`](opencode_api.md) for the full API reference.
|
||||
|
||||
@ -8,7 +8,7 @@ See [`opencode_api.md`](opencode_api.md) for the full API reference.
|
||||
|
||||
```
|
||||
CC Computer
|
||||
└─ ai-helloworld.lua (libaihelloworld.lua)
|
||||
└─ ai.lua (libai.lua)
|
||||
└─ POST /session/:id/message → opencode serve
|
||||
```
|
||||
|
||||
@ -67,6 +67,12 @@ Use the CraftOS shell `set` program at the ComputerCraft console or CraftOS-PC t
|
||||
set opencc.server_url http://<host-ip>:4096
|
||||
```
|
||||
|
||||
For example locally:
|
||||
|
||||
```sh
|
||||
set opencc.server_url http://127.0.0.1:4096
|
||||
```
|
||||
|
||||
With auth:
|
||||
|
||||
```sh
|
||||
@ -82,22 +88,23 @@ set opencc.username myuser
|
||||
- **CraftOS-PC (localhost):** `http://127.0.0.1:4096`
|
||||
- **In-game ATM10:** use your LAN IP (e.g. `192.168.x.x`) — add it to `http.rules` in `config/computercraft-server.toml`
|
||||
|
||||
## 4. Run `ai-helloworld`
|
||||
## 4. Run `ai`
|
||||
|
||||
```
|
||||
ai-helloworld -- ping, reuses existing session
|
||||
ai-helloworld --new -- forget current session, start fresh
|
||||
ai-helloworld --sessions -- list all server sessions with their IDs
|
||||
ai-helloworld --help
|
||||
ai-helloworld --version
|
||||
ai ping -- ping, reuses existing session
|
||||
ai "explain what a turtle is"
|
||||
ai new "start a fresh topic" -- forget current session, start fresh
|
||||
ai sessions -- list all server sessions with their IDs
|
||||
ai --help
|
||||
ai --version
|
||||
```
|
||||
|
||||
Expected output on a working setup: `pong` (default prompt is `reply with exactly: pong`).
|
||||
Expected `ai ping` output on a working setup: `pong`.
|
||||
|
||||
## 5. CraftOS-PC (no Minecraft)
|
||||
|
||||
```bash
|
||||
just trapos --headless -- /programs/ai-helloworld.lua
|
||||
just trapos --headless -- /programs/ai.lua ping
|
||||
```
|
||||
|
||||
Set settings inside the harness before running, or inject them via the test API.
|
||||
@ -109,6 +116,7 @@ Set settings inside the harness before running, or inject them via the test API.
|
||||
| `missing opencc.server_url` | Setting not set | `set opencc.server_url http://...` |
|
||||
| `serveur injoignable` | Server not running or wrong URL | Start `opencode serve`, check URL/port |
|
||||
| `erreur message: HTTP 401` | Wrong password | Check `opencc.password` matches `OPENCODE_SERVER_PASSWORD` |
|
||||
| `session introuvable; lance: ai-helloworld --new` | Session was deleted or server restarted | Run `ai-helloworld --new` |
|
||||
| `missing prompt` | No prompt was passed | Run `ai <prompt>` or `ai ping` |
|
||||
| `session introuvable; lance: ai new <prompt>` | Session was deleted or server restarted | Run `ai new <prompt>` |
|
||||
| `erreur message: HTTP 504` | AI took too long | Retry; consider a faster model |
|
||||
| `reponse vide` | Reply had no text parts | Check opencode logs |
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"tos-boot": "0.2.1",
|
||||
"tos-net": "0.2.0",
|
||||
"tos-ui": "0.2.0",
|
||||
"tos-ai": "0.2.0",
|
||||
"tos-ai": "0.3.0",
|
||||
"trapos": "0.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "tos-ai",
|
||||
"version": "0.2.0",
|
||||
"description": "TrapOS AI hello-world client (opencode proxy)",
|
||||
"version": "0.3.0",
|
||||
"description": "TrapOS AI client for opencode serve",
|
||||
"dependencies": ["tos-core"],
|
||||
"files": [
|
||||
"apis/libaihelloworld.lua",
|
||||
"programs/ai-helloworld.lua"
|
||||
"apis/libai.lua",
|
||||
"programs/ai.lua"
|
||||
],
|
||||
"autostart": []
|
||||
}
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
local _VERSION = '0.2.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 --sessions');
|
||||
print(' ai-helloworld --version');
|
||||
print(' ai-helloworld --help');
|
||||
print();
|
||||
print('settings required:');
|
||||
print(' opencc.server_url');
|
||||
print();
|
||||
print('settings optional:');
|
||||
print(' opencc.username (default: opencode)');
|
||||
print(' opencc.password (Basic Auth password)');
|
||||
print(' opencc.session_id (auto-managed)');
|
||||
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 == '--sessions' then
|
||||
local ok, result = ai.listSessions();
|
||||
if not ok then
|
||||
print(result);
|
||||
return;
|
||||
end
|
||||
if #result == 0 then
|
||||
print('no sessions');
|
||||
else
|
||||
for _, s in ipairs(result) do
|
||||
print((s.id or '?') .. ' ' .. (s.title or '(untitled)'));
|
||||
end
|
||||
end
|
||||
return;
|
||||
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);
|
||||
110
programs/ai.lua
Normal file
110
programs/ai.lua
Normal file
@ -0,0 +1,110 @@
|
||||
local _VERSION = '0.3.0';
|
||||
|
||||
local createAi = require('/apis/libai');
|
||||
|
||||
local args = table.pack(...);
|
||||
|
||||
local function printUsage()
|
||||
print('ai usage:');
|
||||
print();
|
||||
print(' ai <prompt>');
|
||||
print(' ai ping');
|
||||
print(' ai new <prompt>');
|
||||
print(' ai --new <prompt>');
|
||||
print(' ai sessions');
|
||||
print(' ai --sessions');
|
||||
print(' ai --version');
|
||||
print(' ai --help');
|
||||
print();
|
||||
print('settings required:');
|
||||
print(' opencc.server_url');
|
||||
print();
|
||||
print('settings optional:');
|
||||
print(' opencc.username (default: opencode)');
|
||||
print(' opencc.password (Basic Auth password)');
|
||||
print(' opencc.session_id (auto-managed)');
|
||||
end
|
||||
|
||||
local function joinArgs(start)
|
||||
local parts = {};
|
||||
for i = start, args.n do
|
||||
parts[#parts + 1] = args[i];
|
||||
end
|
||||
return table.concat(parts, ' ');
|
||||
end
|
||||
|
||||
local function printSessions(ai)
|
||||
local ok, result = ai.listSessions();
|
||||
if not ok then
|
||||
print(result);
|
||||
return;
|
||||
end
|
||||
if #result == 0 then
|
||||
print('no sessions');
|
||||
else
|
||||
for _, s in ipairs(result) do
|
||||
print((s.id or '?') .. ' ' .. (s.title or '(untitled)'));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function askAndPrint(ai, prompt)
|
||||
local ok, result = ai.ask(prompt);
|
||||
if not ok then
|
||||
print(result);
|
||||
return;
|
||||
end
|
||||
print(result.reply);
|
||||
end
|
||||
|
||||
local command = args[1];
|
||||
|
||||
if command == '--version' or command == '-version' or command == 'version' then
|
||||
print('ai v' .. _VERSION);
|
||||
return;
|
||||
end
|
||||
|
||||
if command == '--help' or command == '-help' or command == 'help' then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
|
||||
if args.n == 0 then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
|
||||
local ai = createAi();
|
||||
|
||||
if (command == 'sessions' or command == '--sessions') and args.n == 1 then
|
||||
printSessions(ai);
|
||||
return;
|
||||
end
|
||||
|
||||
if command == 'ping' and args.n == 1 then
|
||||
local ok, result = ai.ping();
|
||||
if not ok then
|
||||
print(result);
|
||||
return;
|
||||
end
|
||||
print(result.reply);
|
||||
return;
|
||||
end
|
||||
|
||||
if command == 'new' or command == '--new' then
|
||||
local prompt = joinArgs(2);
|
||||
if prompt == '' then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
ai.clearSession();
|
||||
askAndPrint(ai, prompt);
|
||||
return;
|
||||
end
|
||||
|
||||
if string.sub(command, 1, 1) == '-' then
|
||||
printUsage();
|
||||
return;
|
||||
end
|
||||
|
||||
askAndPrint(ai, joinArgs(1));
|
||||
@ -1,5 +1,5 @@
|
||||
local createLibTest = require('/apis/libtest');
|
||||
local createAiHelloWorld = require('/apis/libaihelloworld');
|
||||
local createAi = require('/apis/libai');
|
||||
|
||||
local testlib = createLibTest({ ... });
|
||||
|
||||
@ -60,7 +60,7 @@ local function httpError(code, body)
|
||||
end
|
||||
|
||||
local function sessionResp(id)
|
||||
return response(200, textutils.serializeJSON({ id = id, title = 'cc-helloworld' }));
|
||||
return response(200, textutils.serializeJSON({ id = id, title = 'cc-ai' }));
|
||||
end
|
||||
|
||||
local function messageResp(reply)
|
||||
@ -75,13 +75,13 @@ end
|
||||
testlib.test('base64encode encodes simple ascii', function()
|
||||
-- "Man" -> "TWFu" is the canonical base64 test vector; tested indirectly via Authorization header
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_1'), messageResp('pong') },
|
||||
{ sessionResp('ses_1'), messageResp('ok') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
settingsStub.values['opencc.password'] = 'pass';
|
||||
settingsStub.values['opencc.username'] = 'user';
|
||||
createAiHelloWorld({ http = httpStub, settings = settingsStub }).askHello();
|
||||
createAi({ http = httpStub, settings = settingsStub }).ask('hello');
|
||||
local auth = httpStub.postCalls[1].headers['Authorization'];
|
||||
-- base64('user:pass') = 'dXNlcjpwYXNz'
|
||||
testlib.assertEquals(auth, 'Basic dXNlcjpwYXNz');
|
||||
@ -89,13 +89,13 @@ end);
|
||||
|
||||
testlib.test('base64encode handles padding with one remainder byte', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_1'), messageResp('pong') },
|
||||
{ sessionResp('ses_1'), messageResp('ok') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
settingsStub.values['opencc.password'] = 'x';
|
||||
settingsStub.values['opencc.username'] = 'a';
|
||||
createAiHelloWorld({ http = httpStub, settings = settingsStub }).askHello();
|
||||
createAi({ http = httpStub, settings = settingsStub }).ask('hello');
|
||||
-- base64('a:x') = 'YTp4'
|
||||
testlib.assertEquals(httpStub.postCalls[1].headers['Authorization'], 'Basic YTp4');
|
||||
end);
|
||||
@ -106,7 +106,7 @@ testlib.test('listSessions returns parsed session list', function()
|
||||
local sessions = { { id = 'ses_1', title = 'hello' }, { id = 'ses_2', title = 'world' } };
|
||||
local httpStub = fakeHttp({}, { response(200, textutils.serializeJSON(sessions)) });
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, result = ai.listSessions();
|
||||
|
||||
@ -117,7 +117,7 @@ end);
|
||||
|
||||
testlib.test('listSessions fails when server_url missing', function()
|
||||
local httpStub = fakeHttp({}, {});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = fakeSettings() });
|
||||
local ai = createAi({ http = httpStub, settings = fakeSettings() });
|
||||
|
||||
local ok, err = ai.listSessions();
|
||||
|
||||
@ -131,7 +131,7 @@ end);
|
||||
testlib.test('listSessions fails when server unreachable', function()
|
||||
local httpStub = fakeHttp({}, { nil });
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.listSessions();
|
||||
|
||||
@ -142,7 +142,7 @@ end);
|
||||
testlib.test('listSessions maps HTTP error response codes', function()
|
||||
local httpStub = fakeHttp({}, { httpError(401, '{}') });
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.listSessions();
|
||||
|
||||
@ -150,70 +150,84 @@ testlib.test('listSessions maps HTTP error response codes', function()
|
||||
testlib.assertTrue(string.find(err, 'HTTP 401', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
-- askHello --
|
||||
-- ask --
|
||||
|
||||
testlib.test('askHello creates session then sends message when no session_id', function()
|
||||
testlib.test('ask creates session then sends message when no session_id', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_new'), messageResp('pong') },
|
||||
{ sessionResp('ses_new'), messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host:4096' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, result = ai.askHello();
|
||||
local ok, result = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(ok, tostring(result));
|
||||
testlib.assertEquals(result.reply, 'pong');
|
||||
testlib.assertEquals(result.reply, 'reply');
|
||||
testlib.assertEquals(result.sessionId, 'ses_new');
|
||||
testlib.assertEquals(#httpStub.postCalls, 2);
|
||||
testlib.assertTrue(string.find(httpStub.postCalls[1].url, '/session', 1, true) ~= nil);
|
||||
testlib.assertTrue(string.find(httpStub.postCalls[2].url, '/session/ses_new/message', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello saves new session_id to settings', function()
|
||||
testlib.test('ask creates cc-ai titled sessions', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_abc'), messageResp('pong') },
|
||||
{ sessionResp('ses_new'), messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
ai.askHello();
|
||||
ai.ask('hello');
|
||||
|
||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||
testlib.assertEquals(body.title, 'cc-ai');
|
||||
end);
|
||||
|
||||
testlib.test('ask saves new session_id to settings', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_abc'), messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
ai.ask('hello');
|
||||
|
||||
testlib.assertEquals(settingsStub.values['opencc.session_id'], 'ses_abc');
|
||||
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||
end);
|
||||
|
||||
testlib.test('askHello reuses existing session_id without creating a new session', function()
|
||||
testlib.test('ask reuses existing session_id without creating a new session', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ messageResp('pong') },
|
||||
{ messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_existing',
|
||||
});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok = ai.askHello();
|
||||
local ok = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(ok);
|
||||
testlib.assertEquals(#httpStub.postCalls, 1);
|
||||
testlib.assertTrue(string.find(httpStub.postCalls[1].url, '/session/ses_existing/message', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello sends correct message body', function()
|
||||
testlib.test('ask sends exact prompt text', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ messageResp('pong') },
|
||||
{ messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_1',
|
||||
});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub, prompt = 'my prompt' });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
ai.askHello();
|
||||
ai.ask('my prompt');
|
||||
|
||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||
testlib.assertEquals(#body.parts, 1);
|
||||
@ -221,7 +235,33 @@ testlib.test('askHello sends correct message body', function()
|
||||
testlib.assertEquals(body.parts[1].text, 'my prompt');
|
||||
end);
|
||||
|
||||
testlib.test('askHello concatenates multiple text parts', function()
|
||||
testlib.test('ask rejects missing prompt without HTTP calls', function()
|
||||
local httpStub = fakeHttp({}, {});
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.ask();
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'missing prompt', 1, true) ~= nil);
|
||||
testlib.assertEquals(#httpStub.postCalls, 0);
|
||||
testlib.assertEquals(#httpStub.getCalls, 0);
|
||||
end);
|
||||
|
||||
testlib.test('ask rejects blank prompt without HTTP calls', function()
|
||||
local httpStub = fakeHttp({}, {});
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.ask(' ');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'missing prompt', 1, true) ~= nil);
|
||||
testlib.assertEquals(#httpStub.postCalls, 0);
|
||||
testlib.assertEquals(#httpStub.getCalls, 0);
|
||||
end);
|
||||
|
||||
testlib.test('ask concatenates multiple text parts', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ response(200, textutils.serializeJSON({
|
||||
info = {},
|
||||
@ -238,19 +278,19 @@ testlib.test('askHello concatenates multiple text parts', function()
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_1',
|
||||
});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, result = ai.askHello();
|
||||
local ok, result = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(ok, tostring(result));
|
||||
testlib.assertEquals(result.reply, 'hello world');
|
||||
end);
|
||||
|
||||
testlib.test('askHello fails with missing server_url', function()
|
||||
testlib.test('ask fails with missing server_url', function()
|
||||
local httpStub = fakeHttp({}, {});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = fakeSettings() });
|
||||
local ai = createAi({ http = httpStub, settings = fakeSettings() });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'opencc.server_url', 1, true) ~= nil);
|
||||
@ -259,57 +299,57 @@ testlib.test('askHello fails with missing server_url', function()
|
||||
testlib.assertEquals(#httpStub.postCalls, 0);
|
||||
end);
|
||||
|
||||
testlib.test('askHello fails when server unreachable on session create', function()
|
||||
testlib.test('ask fails when server unreachable on session create', function()
|
||||
local httpStub = fakeHttp({ nil }, {});
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'injoignable', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello fails when server unreachable on message send', function()
|
||||
testlib.test('ask fails when server unreachable on message send', function()
|
||||
local httpStub = fakeHttp({ sessionResp('ses_1'), nil }, {});
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'injoignable', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello maps 401 on message send', function()
|
||||
testlib.test('ask maps 401 on message send', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_1'), response(401, '{}') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'HTTP 401', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello maps HTTP error response on message send', function()
|
||||
testlib.test('ask maps HTTP error response on message send', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_1'), httpError(401, '{}') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, 'HTTP 401', 1, true) ~= nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello on 404 clears session_id and suggests --new', function()
|
||||
testlib.test('ask on 404 clears session_id and suggests ai new', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ response(404, '{}') },
|
||||
{}
|
||||
@ -318,16 +358,16 @@ testlib.test('askHello on 404 clears session_id and suggests --new', function()
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_stale',
|
||||
});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, '--new', 1, true) ~= nil);
|
||||
testlib.assertTrue(string.find(err, 'ai new', 1, true) ~= nil);
|
||||
testlib.assertEquals(settingsStub.values['opencc.session_id'], nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello on HTTP error 404 clears session_id', function()
|
||||
testlib.test('ask on HTTP error 404 clears session_id', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ httpError(404, '{}') },
|
||||
{}
|
||||
@ -336,33 +376,54 @@ testlib.test('askHello on HTTP error 404 clears session_id', function()
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_stale',
|
||||
});
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, err = ai.askHello();
|
||||
local ok, err = ai.ask('hello');
|
||||
|
||||
testlib.assertTrue(not ok);
|
||||
testlib.assertTrue(string.find(err, '--new', 1, true) ~= nil);
|
||||
testlib.assertTrue(string.find(err, 'ai new', 1, true) ~= nil);
|
||||
testlib.assertEquals(settingsStub.values['opencc.session_id'], nil);
|
||||
end);
|
||||
|
||||
testlib.test('askHello omits Authorization header when no password', function()
|
||||
testlib.test('ask omits Authorization header when no password', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ sessionResp('ses_1'), messageResp('pong') },
|
||||
{ sessionResp('ses_1'), messageResp('reply') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||
local ai = createAiHelloWorld({ http = httpStub, settings = settingsStub });
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
ai.askHello();
|
||||
ai.ask('hello');
|
||||
|
||||
testlib.assertEquals(httpStub.postCalls[1].headers['Authorization'], nil);
|
||||
end);
|
||||
|
||||
-- ping --
|
||||
|
||||
testlib.test('ping sends pong prompt', function()
|
||||
local httpStub = fakeHttp(
|
||||
{ messageResp('pong') },
|
||||
{}
|
||||
);
|
||||
local settingsStub = fakeSettings({
|
||||
['opencc.server_url'] = 'http://host',
|
||||
['opencc.session_id'] = 'ses_1',
|
||||
});
|
||||
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||
|
||||
local ok, result = ai.ping();
|
||||
|
||||
testlib.assertTrue(ok, tostring(result));
|
||||
testlib.assertEquals(result.reply, 'pong');
|
||||
local body = textutils.unserializeJSON(httpStub.postCalls[1].body);
|
||||
testlib.assertEquals(body.parts[1].text, 'reply with exactly: pong');
|
||||
end);
|
||||
|
||||
-- clearSession --
|
||||
|
||||
testlib.test('clearSession unsets persisted session id', function()
|
||||
local settingsStub = fakeSettings({ ['opencc.session_id'] = 'ses_old' });
|
||||
local ai = createAiHelloWorld({ http = fakeHttp({}, {}), settings = settingsStub });
|
||||
local ai = createAi({ http = fakeHttp({}, {}), settings = settingsStub });
|
||||
|
||||
ai.clearSession();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user