diff --git a/apis/libai.lua b/apis/libai.lua index e1f6eae..3d6fd9b 100644 --- a/apis/libai.lua +++ b/apis/libai.lua @@ -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 diff --git a/docs/opencode_server_guide.md b/docs/opencode_server_guide.md index 85cf6bb..1fc72e6 100644 --- a/docs/opencode_server_guide.md +++ b/docs/opencode_server_guide.md @@ -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 ``` diff --git a/manifest.json b/manifest.json index e1e9572..219c6e9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "TrapOS", - "version": "0.8.12", + "version": "0.8.13", "branch": "next", "packages": [ "trapos" diff --git a/packages/index.json b/packages/index.json index e4e8304..2e0ada1 100644 --- a/packages/index.json +++ b/packages/index.json @@ -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" } } diff --git a/packages/trapos-ai/ccpm.json b/packages/trapos-ai/ccpm.json index 6a72548..d5cead5 100644 --- a/packages/trapos-ai/ccpm.json +++ b/packages/trapos-ai/ccpm.json @@ -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": [ diff --git a/packages/trapos/ccpm.json b/packages/trapos/ccpm.json index 03d92be..8ad1c25 100644 --- a/packages/trapos/ccpm.json +++ b/packages/trapos/ccpm.json @@ -1,6 +1,6 @@ { "name": "trapos", - "version": "0.8.12", + "version": "0.8.13", "description": "TrapOS full install meta-package", "dependencies": [ "trapos-boot", diff --git a/programs/ai.lua b/programs/ai.lua index 162cf24..458752c 100644 --- a/programs/ai.lua +++ b/programs/ai.lua @@ -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 diff --git a/tests/ai.lua b/tests/ai.lua index fdc7e92..8ae77ca 100644 --- a/tests/ai.lua +++ b/tests/ai.lua @@ -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() },