fix(ai): cap opencode timeouts

This commit is contained in:
Guillaume ARM 2026-06-11 21:43:45 +02:00
parent 150e847475
commit 79677e2742
8 changed files with 70 additions and 16 deletions

View File

@ -1,8 +1,9 @@
local PING_PROMPT = 'reply with exactly: pong';
local DEFAULT_TIMEOUT_SECONDS = 120;
local MAX_TIMEOUT_SECONDS = 120;
local DEFAULT_TIMEOUT_SECONDS = 60;
local MAX_TIMEOUT_SECONDS = 60;
local DEFAULT_POLL_TIMEOUT_SECONDS = 600;
local MAX_POLL_TIMEOUT_SECONDS = 600;
local DEFAULT_POLL_INTERVAL_SECONDS = 2;
local DEFAULT_LUA_EXEC_MAX_RETRIES = 2;
local DEFAULT_LUA_EXEC_TIMEOUT_SECONDS = 5;
@ -226,6 +227,7 @@ local function createAi(opts)
if raw == nil then raw = settingsLib.get('opencc.poll_timeout_seconds'); end
local n = tonumber(raw);
if not n or n <= 0 then n = DEFAULT_POLL_TIMEOUT_SECONDS; end
if n > MAX_POLL_TIMEOUT_SECONDS then n = MAX_POLL_TIMEOUT_SECONDS; end
return n;
end

View File

@ -126,7 +126,7 @@ set opencc.model_id claude-opus-4-7
Optional timeout settings:
```sh
set opencc.timeout_seconds 120
set opencc.timeout_seconds 60
set opencc.poll_timeout_seconds 600
set opencc.poll_interval_seconds 2
```

View File

@ -1,6 +1,6 @@
{
"name": "TrapOS",
"version": "0.8.12",
"version": "0.8.13",
"branch": "next",
"packages": [
"trapos"

View File

@ -5,8 +5,8 @@
"trapos-boot": "0.3.2",
"trapos-net": "0.3.0",
"trapos-ui": "0.2.2",
"trapos-ai": "0.6.10",
"trapos-ai": "0.6.11",
"trapos-sandbox": "0.2.2",
"trapos": "0.8.12"
"trapos": "0.8.13"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "trapos-ai",
"version": "0.6.10",
"version": "0.6.11",
"description": "TrapOS AI client for opencode serve",
"dependencies": ["trapos-core"],
"files": [

View File

@ -1,6 +1,6 @@
{
"name": "trapos",
"version": "0.8.12",
"version": "0.8.13",
"description": "TrapOS full install meta-package",
"dependencies": [
"trapos-boot",

View File

@ -52,8 +52,8 @@ local function printUsage()
print(' opencc.agent (e.g. atm10-expert)');
print(' opencc.provider_id (e.g. anthropic)');
print(' opencc.model_id (e.g. claude-opus-4-7)');
print(' opencc.timeout_seconds (per HTTP call, max 120)');
print(' opencc.poll_timeout_seconds (default: 600)');
print(' opencc.timeout_seconds (per HTTP call, max 60)');
print(' opencc.poll_timeout_seconds (default/max: 600)');
print(' opencc.poll_interval_seconds (default: 2)');
end

View File

@ -249,6 +249,20 @@ testlib.test('listSessions sends configured directory query', function()
'http://host/session?directory=%2FUsers%2Fgarm%2Ftrap%2Fcc-libs');
end);
testlib.test('listSessions caps HTTP timeout at one minute', function()
local httpStub = fakeHttp({}, { response(200, '[]') });
local settingsStub = fakeSettings({
['opencc.server_url'] = 'http://host',
['opencc.timeout_seconds'] = 120,
});
local ai = createAi({ http = httpStub, settings = settingsStub });
local ok = ai.listSessions();
testlib.assertTrue(ok);
testlib.assertEquals(httpStub.getCalls[1].timeout, 60);
end);
testlib.test('listSessions retries with persisted session directory when list is empty', function()
local scopedSessions = {
{ id = 'ses_existing', title = 'existing', time = { updated = 20 } },
@ -847,7 +861,7 @@ testlib.test('ask polling default timeout allows ten minute replies', function()
testlib.assertEquals(#httpStub.getCalls, 3);
end);
testlib.test('ask uses two minute HTTP timeout by default', function()
testlib.test('ask uses one minute HTTP timeout by default', function()
local httpStub = fakeHttp(
{ sessionResp('ses_1'), messageResp('reply') },
{}
@ -858,11 +872,11 @@ testlib.test('ask uses two minute HTTP timeout by default', function()
local ok = ai.ask('hello');
testlib.assertTrue(ok);
testlib.assertEquals(httpStub.postCalls[1].timeout, 120);
testlib.assertEquals(httpStub.postCalls[2].timeout, 120);
testlib.assertEquals(httpStub.postCalls[1].timeout, 60);
testlib.assertEquals(httpStub.postCalls[2].timeout, 60);
end);
testlib.test('ask caps per-call HTTP timeout at two minutes', function()
testlib.test('ask caps per-call HTTP timeout at one minute', function()
local httpStub = fakeHttp(
{ sessionResp('ses_1'), messageResp('reply') },
{}
@ -876,8 +890,8 @@ testlib.test('ask caps per-call HTTP timeout at two minutes', function()
local ok = ai.ask('hello');
testlib.assertTrue(ok);
testlib.assertEquals(httpStub.postCalls[1].timeout, 120);
testlib.assertEquals(httpStub.postCalls[2].timeout, 120);
testlib.assertEquals(httpStub.postCalls[1].timeout, 60);
testlib.assertEquals(httpStub.postCalls[2].timeout, 60);
end);
testlib.test('ask polling times out', function()
@ -921,6 +935,44 @@ testlib.test('ask polling times out', function()
testlib.assertEquals(#elState.lastLoop.inspect().pending, 0);
end);
testlib.test('ask caps polling timeout at ten minutes', function()
local httpStub = fakeHttp(
{ sessionResp('ses_1'), asyncResp() },
{
messageListResp({ userMessage('msg_1', 'hello'), assistantMessage('msg_2', 'partial', false) }),
messageListResp({ userMessage('msg_1', 'hello'), assistantMessage('msg_2', 'partial', false) }),
messageListResp({ userMessage('msg_1', 'hello'), assistantMessage('msg_2', 'partial', false) }),
}
);
local settingsStub = fakeAsyncSettings({
['opencc.poll_timeout_seconds'] = 1200,
});
local now = 0;
local elFactory = fakeEventloopFactory();
local advancingFactory = function()
local loop = elFactory();
local origSet = loop.setTimeout;
loop.setTimeout = function(fn, delay)
now = now + (delay or 0);
return origSet(fn, delay);
end
return loop;
end;
local ai = createAi({
http = httpStub,
settings = settingsStub,
now = function() return now; end,
eventloop = advancingFactory,
});
local ok, err = ai.ask('hello', { messageId = 'msg_1', pollIntervalSeconds = 300 });
testlib.assertTrue(not ok);
testlib.assertTrue(string.find(err, 'delai depasse', 1, true) ~= nil);
testlib.assertEquals(#httpStub.getCalls, 3);
testlib.assertEquals(now, 600);
end);
testlib.test('ask polling does not call os.sleep', function()
local httpStub = fakeHttp(
{ sessionResp('ses_1'), asyncResp() },