@@ -10,7 +10,7 @@ import { mockMeteringProxy } from "./provider-meter";
1010import { readHostEvents , stageEventObserver } from "./event-observer" ;
1111import { redactedLines } from "./credential-protocol" ;
1212
13- export type HostLane = "opencode-1" | "opencode-2" | "omp" ;
13+ export type HostLane = "opencode-1" | "opencode-2" | "pi" | " omp";
1414export interface HostAdapterOptions {
1515 sourceRoot : string ;
1616 mockProviderURL : string ;
@@ -22,8 +22,13 @@ export interface HostAdapterOptions {
2222 classify ?( path : string , body : string ) : CallBound [ "kind" ] ;
2323 magicContext : Record < string , unknown > ;
2424 outputCeiling : number ;
25+ /** Pin an explicitly installed release when comparing host versions. */
26+ hostVersion ?: string ;
27+ modelOptions ?: Record < string , unknown > ;
28+ reasoning ?: boolean ;
2529 timeoutMs ?: number ;
2630 startupTimeoutMs ?: number ;
31+ providerTimeoutMs ?: number ;
2732 expectTurnTwo ?: boolean ;
2833 /** Synthetic probes can add a second foreground turn and explicitly request wrapup. */
2934 forceWrapup ?: boolean ;
@@ -51,8 +56,9 @@ export function hostAdapter(lane: HostLane, options: HostAdapterOptions): Activa
5156 let hostPid : number | undefined ;
5257 const processProofs = new Map < number , Isolation > ( ) ;
5358 const logFilters : ReturnType < typeof redactedLines > [ ] = [ ] ;
54- const binary = ( root : string ) => join ( root , "host-dist/node_modules/.bin" , lane === "omp" ? "omp" : "opencode" ) ;
55- const entrypoint = ( root : string ) => join ( root , lane === "omp" ? "packages/pi-plugin/dist/index.js" : lane === "opencode-2" ? "packages/plugin/dist/v2/server.js" : "packages/plugin/dist/index.js" ) ;
59+ const isPi = lane === "pi" || lane === "omp" ;
60+ const binary = ( root : string ) => join ( root , "host-dist/node_modules/.bin" , isPi ? lane : "opencode" ) ;
61+ const entrypoint = ( root : string ) => join ( root , isPi ? "packages/pi-plugin/dist/index.js" : lane === "opencode-2" ? "packages/plugin/dist/v2/server.js" : "packages/plugin/dist/index.js" ) ;
5662 return processActivationTransport ( {
5763 stage : ( root ) => ( { argv : [ process . execPath , installer , "stage" , options . sourceRoot , root , lane ] , cwd : root } ) ,
5864 build : ( root ) => ( { argv : [ process . execPath , installer , "build" , options . sourceRoot , root , lane ] , cwd : root } ) ,
@@ -77,12 +83,13 @@ export function hostAdapter(lane: HostLane, options: HostAdapterOptions): Activa
7783 const result = spawnSync ( binary ( input . installRoot ) , [ "--version" ] , { cwd : work , env : { PATH : process . env . PATH , ...versionEnv } , encoding : "utf8" } ) ;
7884 versionOutput = `${ result . stdout ?? "" } ${ result . stderr ?? "" } ` . trim ( ) ;
7985 version = versionOutput . match ( / \d + \. \d + \. \d + / ) ?. [ 0 ] ?? "" ;
80- if ( result . status !== 0 || ( lane === "opencode-1" ? ! / ^ 1 \. 1 8 \. ( 3 [ 1 - 9 ] | [ 4 - 9 ] \d | \d { 3 , } ) $ / . test ( version ) : lane === "opencode-2" ? version !== "2.0.22" : ! / 1 8 \. 1 2 \. \d + / . test ( version ) ) )
86+ if ( result . status !== 0 || ( options . hostVersion ? version !== options . hostVersion : lane === "opencode-1" ? ! / ^ 1 \. 1 8 \. ( 3 [ 1 - 9 ] | [ 4 - 9 ] \d | \d { 3 , } ) $ / . test ( version ) : lane === "opencode-2" ? version !== "2.0.22" : lane === "pi" ? version !== "1.4.0 " : ! / 1 8 \. 1 2 \. \d + / . test ( version ) ) )
8187 throw new Error ( "host_version_not_pinned" ) ;
8288 proxy = await mockMeteringProxy ( { upstream : options . mockProviderURL , meter : input . meter ,
8389 secrets, forwardCredentials : ! ! options . providerKeyEnv || ! ! options . grantFree , paidProvider : options . paidProvider ,
8490 onProviderAttempt : options . onProviderAttempt ,
85- outputCeiling : options . outputCeiling ,
91+ outputCeiling : options . outputCeiling ,
92+ timeoutMs : options . providerTimeoutMs ,
8693 // Prompt text can mention another stage; it is not evidence of this dispatch's purpose.
8794 classify : options . classify ?? ( ( ) => "unclassified" ) ,
8895 async beforeDispatch ( ) {
@@ -96,7 +103,7 @@ export function hostAdapter(lane: HostLane, options: HostAdapterOptions): Activa
96103 observer = stageEventObserver ( root , lane ) ;
97104 input . env . MC_HOST_EVENT_JSONL = observer . eventsPath ;
98105 writeConfigs ( lane , input . installRoot , work , hostEnv , proxy . url , options , observer ) ;
99- return { argv : lane === "omp" ? [ binary ( input . installRoot ) , "--mode" , "rpc" , "--extension" , entrypoint ( input . installRoot ) , "--extension" , observer . entrypoint ]
106+ return { argv : isPi ? [ binary ( input . installRoot ) , "--mode" , "rpc" , "--extension" , entrypoint ( input . installRoot ) , "--extension" , observer . entrypoint ]
100107 : [ binary ( input . installRoot ) , "serve" , "--hostname" , "127.0.0.1" , "--port" , "0" , "--print-logs" ] , cwd : work } ;
101108 } ,
102109 async connect ( input ) {
@@ -113,7 +120,7 @@ export function hostAdapter(lane: HostLane, options: HostAdapterOptions): Activa
113120 let observedStore : string | null = null ;
114121 let sessionId = "" ;
115122 let prompt : ( text : string ) => Promise < unknown > ;
116- if ( lane === "omp" ) {
123+ if ( isPi ) {
117124 const lines = createInterface ( { input : input . stdout } ) ;
118125 const pending = new Map < string , { resolve ( value : any ) : void ; reject ( error : Error ) : void } > ( ) ;
119126 const settled : Array < ( ) => void > = [ ] ;
@@ -143,6 +150,7 @@ export function hostAdapter(lane: HostLane, options: HostAdapterOptions): Activa
143150 for ( ; ; ) {
144151 if ( Date . now ( ) >= end ) throw new Error ( "omp_settlement_timeout" ) ;
145152 const value = ( await rpc ( "get_state" ) ) . data ;
153+ if ( lane === "pi" && value ?. isStreaming === false ) return value ;
146154 if ( typeof value ?. isSettled !== "boolean" || typeof value ?. hasPendingAsyncWork !== "boolean" )
147155 throw new Error ( "omp_settlement_unobservable" ) ;
148156 if ( value . isSettled && ! value . isStreaming && ! value . hasPendingAsyncWork ) return {
@@ -305,37 +313,39 @@ async function deadline<T>(promise: Promise<T>, timeout: number): Promise<T> {
305313 finally { clearTimeout ( timer ! ) ; }
306314}
307315
308- function writeConfigs ( lane : HostLane , install : string , work : string , env : Record < string , string > , endpoint : string , options : HostAdapterOptions , observer : ReturnType < typeof stageEventObserver > ) {
316+ export function writeConfigs ( lane : HostLane , install : string , work : string , env : Record < string , string > , endpoint : string , options : HostAdapterOptions , observer : ReturnType < typeof stageEventObserver > ) {
317+ const isPi = lane === "pi" || lane === "omp" ;
309318 const providerId = options . providerId ?? "mock" ;
310319 const model = `${ providerId } /${ options . modelId } ` ;
311320 const plugin = lane === "opencode-2" ? join ( install , "packages/plugin" ) : pathToFileURL ( join ( install , "packages/plugin/dist/index.js" ) ) . href ;
312321 const limits = { context : 200_000 , output : options . outputCeiling } ;
313322 // OpenCode's grant-free Zen path uses the public marker, not an account credential.
314- const apiKey = options . grantFree ? "public" : options . providerKeyEnv ? ( lane === "omp" ? options . providerKeyEnv : `{env:${ options . providerKeyEnv } }` ) : "mock-only-key" ;
323+ const apiKey = options . grantFree ? "public" : options . providerKeyEnv ? ( isPi ? options . providerKeyEnv : `{env:${ options . providerKeyEnv } }` ) : "mock-only-key" ;
315324 const config = lane === "opencode-2" ? { plugins : [ plugin , observer . directory ] , model,
316- providers : { [ providerId ] : { package : options . providerPackage ?? "@ai-sdk/ openai-compatible" , settings : { baseURL : `${ endpoint } /v1` , apiKey } , models : { [ options . modelId ] : { name : options . modelId , limit : limits } } } } ,
325+ providers : { [ providerId ] : { package : options . providerPackage ?? "@opencode/ai/providers/ openai-compatible" , settings : { baseURL : `${ endpoint } /v1` , apiKey } , ... ( options . modelOptions ? { body : options . modelOptions } : { } ) , models : { [ options . modelId ] : { name : options . modelId , limit : limits } } } } ,
317326 compaction : { auto : false } ,
318327 } : { plugin : [ plugin , pathToFileURL ( observer . entrypoint ) . href ] , model, small_model : model , enabled_providers : [ providerId ] , autoupdate : false ,
319- provider : { [ providerId ] : { npm : options . providerPackage ?? "@ai-sdk/openai-compatible" , options : { baseURL : `${ endpoint } /v1` , apiKey } , models : { [ options . modelId ] : { name : options . modelId , limit : limits } } } } ,
328+ provider : { [ providerId ] : { npm : options . providerPackage ?? "@ai-sdk/openai-compatible" , options : { baseURL : `${ endpoint } /v1` , apiKey } , models : { [ options . modelId ] : { name : options . modelId , limit : limits , options : options . modelOptions , reasoning : options . reasoning } } } } ,
320329 compaction : { auto : false , prune : false } , agent : { title : { disable : true } , summary : { disable : true } } ,
321330 } ;
322- if ( lane === "omp" ) {
331+ if ( isPi ) {
323332 mkdirSync ( env . PI_CODING_AGENT_DIR , { recursive : true } ) ;
324333 writeFileSync ( join ( env . PI_CODING_AGENT_DIR , "config.yml" ) , JSON . stringify ( { defaultProvider : providerId , defaultModel : options . modelId ,
325334 enabledModels : [ model ] , retry : { enabled : false } , compaction : { enabled : false } , quietStartup : true , enableInstallTelemetry : false , tools : { xdev : false , intentTracing : false } } ) ) ;
326335 const models = JSON . stringify ( { providers : { [ providerId ] : { api : options . piApi ?? "anthropic-messages" , baseUrl : endpoint , apiKey,
327336 ...( options . piAuth ? { auth : options . piAuth } : { } ) ,
328- models : [ { id : options . modelId , name : options . modelId , input : [ "text" ] , contextWindow : 200_000 , maxTokens : options . outputCeiling , reasoning : false , cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } } ] } } } ) ;
337+ models : [ { id : options . modelId , name : options . modelId , input : [ "text" ] , contextWindow : 200_000 , maxTokens : options . outputCeiling , reasoning : options . reasoning ?? false , cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } } ] } } } ) ;
329338 // OMP 18.12 reads YAML first; updating only JSON leaves a previously imported endpoint active.
330339 writeFileSync ( join ( env . PI_CODING_AGENT_DIR , "models.yml" ) , models ) ;
331340 writeFileSync ( join ( env . PI_CODING_AGENT_DIR , "models.json" ) , models ) ;
341+ if ( lane === "pi" ) writeFileSync ( join ( env . PI_CODING_AGENT_DIR , "settings.json" ) , JSON . stringify ( { defaultProvider : providerId , defaultModel : options . modelId , compaction : { enabled : false } , retry : { enabled : false } } ) ) ;
332342 } else if ( lane === "opencode-2" ) writeFileSync ( join ( work , "opencode.json" ) , JSON . stringify ( config ) ) ;
333343 else {
334344 if ( existsSync ( join ( work , "opencode.json" ) ) ) unlinkSync ( join ( work , "opencode.json" ) ) ;
335345 writeFileSync ( join ( env . XDG_CONFIG_HOME , "opencode.json" ) , JSON . stringify ( config ) ) ;
336346 }
337347 const settings = { enabled : true , memory : { enabled : true , auto_search : { enabled : false } , git_commit_indexing : { enabled : false } } ,
338- embedding : { provider : "off" } , dreamer : { disable : true } , historian : { [ lane === "omp" ? "omp" : "opencode" ] : { model } } , ...options . magicContext } ;
348+ embedding : { provider : "off" } , dreamer : { disable : true } , historian : { [ isPi ? lane : "opencode" ] : { model } } , ...options . magicContext } ;
339349 mkdirSync ( join ( env . XDG_CONFIG_HOME , "cortexkit" ) , { recursive : true } ) ;
340350 writeFileSync ( join ( env . XDG_CONFIG_HOME , "cortexkit/magic-context.jsonc" ) , JSON . stringify ( settings ) ) ;
341351}
0 commit comments