feat(ai): generalize opencode client

This commit is contained in:
Guillaume ARM 2026-06-09 04:18:45 +02:00
parent 3d26b67003
commit 65d8a927b8
9 changed files with 277 additions and 150 deletions

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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 |

View File

@ -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"
}
}

View File

@ -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": []
}

View File

@ -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
View 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));

View File

@ -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();