---@diagnostic disable: need-check-nil, undefined-field -- Guards the Pi assumptions pivi is built on. -- The child runs with the user's own Pi configuration, because pivi's promise -- is bring your own Pi: extensions, skills, prompts, and providers keep loading -- under Pi's normal rules. -- These run a real `pi --mode rpc` child, so they are opt in: -- PIVI_CONTRACT=1 just test tests/contract -- They make no model call, so they cost nothing beyond process startup. local jsonl = require 'bugabinga.pivi.jsonl' local rpc = require 'bugabinga.pivi.rpc' local help = require 'bugabinga.pivi.help' local runtime = require 'bugabinga.pivi.runtime' local enabled = vim.env.PIVI_CONTRACT == '1' and vim.fn.executable 'pi' == 1 -- Starting Pi with the user's full configuration is the expensive part, so one -- child per command line serves every question asked of it. local children = {} local child_for = function( label, command_line ) if children[label] then return children[label] end local child = { frames = {}, } child.client = rpc.start { command = command_line, cwd = vim.uv.cwd(), on_event = function( frame ) table.insert( child.frames, frame ) end, on_response = function( frame ) table.insert( child.frames, frame ) end, } children[label] = child return child end local ask = function( commands, predicate, command_line ) local child = child_for( command_line and 'help' or 'default', command_line or { 'pi', '--mode', 'rpc', '--no-session', } ) local first = #child.frames + 1 for _, command in ipairs( commands ) do child.client.request( command ) end local since = function() return vim.list_slice( child.frames, first, #child.frames ) end vim.wait( 120000, function() return predicate( since() ) end, 50 ) return since() end local shutdown = function() for _, child in pairs( children ) do child.client.stop() -- A child that outlives the run keeps the whole suite from exiting. vim.wait( 15000, function() return not child.client.is_running() end, 50 ) end children = {} end local response_for = function( frames, name ) for _, frame in ipairs( frames ) do if frame.type == 'response' and frame.command == name then return frame end end return nil end describe( 'pi rpc contract', function() if not enabled then it( 'is skipped without PIVI_CONTRACT=1 and pi on PATH', function() assert.is_true( true ) end ) return end it( 'answers get_state with the fields pivi caches', function() local frames = ask( { { type = 'get_state', }, }, function( seen ) return response_for( seen, 'get_state' ) ~= nil end ) local answer = response_for( frames, 'get_state' ) assert.is_true( answer.success ) assert.is_boolean( answer.data.isStreaming ) assert.is_boolean( answer.data.isCompacting ) assert.is_string( answer.data.thinkingLevel ) end ) it( 'lists only commands a non interactive client can invoke', function() local frames = ask( { { type = 'get_commands', }, }, function( seen ) return response_for( seen, 'get_commands' ) ~= nil end ) local answer = response_for( frames, 'get_commands' ) assert.is_true( answer.success ) for _, entry in ipairs( answer.data.commands ) do assert.is_string( entry.name ) assert.is_true( entry.source == 'extension' or entry.source == 'prompt' or entry.source == 'skill' ) assert.is_not.equal( 'settings', entry.name ) assert.is_not.equal( 'hotkeys', entry.name ) end end ) it( 'reports a failed command instead of closing the channel', function() local frames = ask( { { type = 'set_model', provider = 'nope', modelId = 'nope', }, { type = 'get_available_thinking_levels', }, }, function( seen ) return response_for( seen, 'get_available_thinking_levels' ) ~= nil end ) local failed = response_for( frames, 'set_model' ) assert.is_false( failed.success ) assert.is_string( failed.error ) assert.is_true( response_for( frames, 'get_available_thinking_levels' ).success ) end ) it( 'keeps the user configuration loaded', function() local frames = ask( { { type = 'get_commands', }, }, function( seen ) return response_for( seen, 'get_commands' ) ~= nil end ) local sources = {} for _, entry in ipairs( response_for( frames, 'get_commands' ).data.commands ) do sources[entry.source] = true end assert.is_true( next( sources ) ~= nil ) end ) it( 'accepts the flags a help run uses and loads the pivi skill', function() local frames = ask( { { type = 'get_commands', }, }, function( seen ) return response_for( seen, 'get_commands' ) ~= nil end, help.build_command {} ) local answer = response_for( frames, 'get_commands' ) assert.is_true( answer.success ) local names = {} for _, entry in ipairs( answer.data.commands ) do names[entry.name] = true end assert.is_true( names['skill:pivi'] ) for _, frame in ipairs( frames ) do assert.is_not.equal( 'extension_error', frame.type ) end end ) it( 'loads the companion extension without error', function() assert.is_string( runtime.extension_path() ) assert.is_string( runtime.skill_path() ) end ) after_each( function() -- Every question must be answerable by an already running child. for _, child in pairs( children ) do assert.is_true( child.client.is_running() ) end end ) it( 'frames every record as strict line feed delimited JSON', function() local frames = ask( { { type = 'get_state', }, }, function( seen ) return response_for( seen, 'get_state' ) ~= nil end ) assert.is_true( #frames > 0 ) for _, frame in ipairs( frames ) do local encoded = vim.json.encode( frame ) assert.is_nil( encoded:find( '\n', 1, true ) ) assert.is_table( jsonl.parse( encoded ) ) end end ) it( 'ends every child it started', function() shutdown() assert.same( {}, children ) end ) end )