From 93a4ad2395ef118f2b2cbacc8af4698ffe931225 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Thu, 11 Jun 2026 23:28:41 +0200 Subject: [PATCH] feat(ai): support opencode variant setting --- apis/libai.lua | 16 ++++++++ manifest.json | 2 +- packages/index.json | 4 +- packages/trapos-ai/ccpm.json | 2 +- packages/trapos/ccpm.json | 2 +- programs/ai.lua | 1 + tests/ai.lua | 80 ++++++++++++++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 5 deletions(-) diff --git a/apis/libai.lua b/apis/libai.lua index 1763ce0..679f059 100644 --- a/apis/libai.lua +++ b/apis/libai.lua @@ -9,6 +9,7 @@ local DEFAULT_LUA_EXEC_MAX_RETRIES = 2; local DEFAULT_LUA_EXEC_TIMEOUT_SECONDS = 5; local DEFAULT_SESSION_SETTING_KEY = 'opencc.session_id'; local DEFAULT_AGENT_SETTING_KEY = 'opencc.agent'; +local DEFAULT_VARIANT_SETTING_KEY = 'opencc.variant'; local createHttp = require('/apis/libhttp'); @@ -216,6 +217,13 @@ local function createAi(opts) return agent; end + local function resolveVariant(options) + local variant = options.variant; + if variant == nil then variant = settingsLib.get(DEFAULT_VARIANT_SETTING_KEY); end + if isBlank(variant) then return nil; end + return variant; + end + local function resolveConfig(options) local url = options.serverUrl or settingsLib.get('opencc.server_url'); if not url or url == '' then @@ -233,6 +241,7 @@ local function createAi(opts) providerID = providerId, modelID = modelId, agent = resolveAgent(options), + variant = resolveVariant(options), timeoutSeconds = resolveTimeout(options), pollTimeoutSeconds = resolvePollTimeout(options), pollIntervalSeconds = resolvePollInterval(options), @@ -250,6 +259,9 @@ local function createAi(opts) if cfg.agent then body.agent = cfg.agent; end + if cfg.variant then + body.variant = cfg.variant; + end return body; end @@ -263,6 +275,9 @@ local function createAi(opts) if cfg.agent then body.agent = cfg.agent; end + if cfg.variant then + body.variant = cfg.variant; + end return body; end @@ -677,6 +692,7 @@ local function createAi(opts) providerID = options.providerID, modelID = options.modelID, agent = options.agent, + variant = options.variant, timeoutSeconds = options.timeoutSeconds, }; end diff --git a/manifest.json b/manifest.json index 2cfc105..a2e732f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "TrapOS", - "version": "0.8.15", + "version": "0.8.16", "branch": "next", "packages": [ "trapos" diff --git a/packages/index.json b/packages/index.json index 7016312..2a0a30f 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.13", + "trapos-ai": "0.6.14", "trapos-sandbox": "0.2.2", - "trapos": "0.8.15" + "trapos": "0.8.16" } } diff --git a/packages/trapos-ai/ccpm.json b/packages/trapos-ai/ccpm.json index 61d76e4..7de4a9f 100644 --- a/packages/trapos-ai/ccpm.json +++ b/packages/trapos-ai/ccpm.json @@ -1,6 +1,6 @@ { "name": "trapos-ai", - "version": "0.6.13", + "version": "0.6.14", "description": "TrapOS AI client for opencode serve", "dependencies": ["trapos-core"], "files": [ diff --git a/packages/trapos/ccpm.json b/packages/trapos/ccpm.json index d8b4cc4..ebbee5b 100644 --- a/packages/trapos/ccpm.json +++ b/packages/trapos/ccpm.json @@ -1,6 +1,6 @@ { "name": "trapos", - "version": "0.8.15", + "version": "0.8.16", "description": "TrapOS full install meta-package", "dependencies": [ "trapos-boot", diff --git a/programs/ai.lua b/programs/ai.lua index 458752c..16b59aa 100644 --- a/programs/ai.lua +++ b/programs/ai.lua @@ -50,6 +50,7 @@ local function printUsage() print(' opencc.session_id (auto-managed)'); print(' opencc.directory (optional session list scope)'); print(' opencc.agent (e.g. atm10-expert)'); + print(' opencc.variant (e.g. low)'); 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 60)'); diff --git a/tests/ai.lua b/tests/ai.lua index 6a03fe5..ec18eaa 100644 --- a/tests/ai.lua +++ b/tests/ai.lua @@ -588,6 +588,42 @@ testlib.test('ask omits blank agent setting', function() testlib.assertEquals(body.agent, nil); end); +testlib.test('ask includes variant when setting is set', function() + local httpStub = fakeHttp( + { messageResp('reply') }, + {} + ); + local settingsStub = fakeSettings({ + ['opencc.server_url'] = 'http://host', + ['opencc.session_id'] = 'ses_1', + ['opencc.variant'] = 'low', + }); + local ai = createAi({ http = httpStub, settings = settingsStub }); + + ai.ask('hello'); + + local body = textutils.unserializeJSON(httpStub.postCalls[1].body); + testlib.assertEquals(body.variant, 'low'); +end); + +testlib.test('ask omits blank variant setting', function() + local httpStub = fakeHttp( + { messageResp('reply') }, + {} + ); + local settingsStub = fakeSettings({ + ['opencc.server_url'] = 'http://host', + ['opencc.session_id'] = 'ses_1', + ['opencc.variant'] = ' ', + }); + local ai = createAi({ http = httpStub, settings = settingsStub }); + + ai.ask('hello'); + + local body = textutils.unserializeJSON(httpStub.postCalls[1].body); + testlib.assertEquals(body.variant, nil); +end); + testlib.test('ask includes model when provider_id and model_id are set', function() local httpStub = fakeHttp( { messageResp('reply') }, @@ -681,6 +717,24 @@ testlib.test('ask options providerID/modelID override settings', function() testlib.assertEquals(body.model.modelID, 'gpt-5'); end); +testlib.test('ask options variant overrides setting', function() + local httpStub = fakeHttp( + { messageResp('reply') }, + {} + ); + local settingsStub = fakeSettings({ + ['opencc.server_url'] = 'http://host', + ['opencc.session_id'] = 'ses_1', + ['opencc.variant'] = 'high', + }); + local ai = createAi({ http = httpStub, settings = settingsStub }); + + ai.ask('hello', { variant = 'low' }); + + local body = textutils.unserializeJSON(httpStub.postCalls[1].body); + testlib.assertEquals(body.variant, 'low'); +end); + testlib.test('ask generates opencode-compatible message ids', function() local httpStub = fakeHttp( { messageResp('reply') }, @@ -727,6 +781,32 @@ testlib.test('ask includes agent in async prompts', function() testlib.assertEquals(body.agent, 'atm10-expert'); end); +testlib.test('ask includes variant in async prompts', function() + local httpStub = fakeHttp( + { asyncResp() }, + { + messageListResp({ assistantMessage('msg_1', 'reply', true) }), + } + ); + local settingsStub = fakeAsyncSettings({ + ['opencc.session_id'] = 'ses_1', + ['opencc.variant'] = 'low', + }); + local elFactory = fakeEventloopFactory(); + local ai = createAi({ + http = httpStub, + settings = settingsStub, + now = function() return 10; end, + eventloop = elFactory, + }); + + local ok = ai.ask('hello', { messageId = 'msg_1' }); + + testlib.assertTrue(ok); + local body = textutils.unserializeJSON(httpStub.postCalls[1].body); + testlib.assertEquals(body.variant, 'low'); +end); + testlib.test('ask includes caller context in async prompts', function() local httpStub = fakeHttp( { asyncResp() },