11import { readFileSync , existsSync } from "fs" ;
22import { homedir } from "os" ;
3- import { join , dirname } from "path" ;
4- import { fileURLToPath } from "url" ;
3+ import { join } from "path" ;
54import {
65 generateOpencodeModels ,
76 loadCatalogFromLocalCommandCode ,
@@ -14,11 +13,10 @@ import {
1413 pluginStateDir ,
1514} from "./src/startup.js" ;
1615import type { CatalogManifest } from "./src/manifest.js" ;
16+ import { generateV2Models } from "./src/v2models.js" ;
17+ import { catalogPaths } from "./src/paths.js" ;
1718
18- const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
19- const MODELS_PATH = join ( __dirname , "models.json" ) ;
20- const VERSION_PATH = join ( __dirname , "_version.txt" ) ;
21- const MANIFEST_PATH = join ( __dirname , "manifest.json" ) ;
19+ const { models : MODELS_PATH , manifest : MANIFEST_PATH , version : VERSION_PATH } = catalogPaths ( ) ;
2220
2321interface PluginFileConfig {
2422 disableModelSync ?: boolean ;
@@ -72,7 +70,94 @@ function readBundledVersion(): string | null {
7270 }
7371}
7472
75- export default async function commandcodePlugin ( ) {
73+ type CatalogLoad = {
74+ models : ModelEntry [ ] ;
75+ catalogSource : "bundled" | "cache" | "opt-in-local" ;
76+ commandCodeVersion : string | null ;
77+ degraded : boolean ;
78+ degradedReason : string | null ;
79+ } ;
80+
81+ function loadCatalogEntries ( ) : CatalogLoad {
82+ const pluginCfg = loadPluginConfig ( ) ;
83+ const override =
84+ pluginCfg . commandCodePackagePath ?. trim ( ) || process . env . COMMANDCODE_PACKAGE_PATH ?. trim ( ) || "" ;
85+
86+ let models : ModelEntry [ ] = [ ] ;
87+ let catalogSource : CatalogLoad [ "catalogSource" ] = "bundled" ;
88+ let commandCodeVersion : string | null = null ;
89+ let degraded = false ;
90+ let degradedReason : string | null = null ;
91+
92+ if ( override ) {
93+ const localCatalog = loadCatalogFromLocalCommandCode ( { packagePath : override } ) ;
94+ if ( localCatalog && localCatalog . models . length > 0 ) {
95+ models = localCatalog . models ;
96+ catalogSource = "opt-in-local" ;
97+ commandCodeVersion = localCatalog . version ;
98+ }
99+ }
100+
101+ if ( models . length === 0 ) {
102+ const bundled = loadBundledModels ( ) ;
103+ if ( bundled ) {
104+ models = bundled ;
105+ catalogSource = "bundled" ;
106+ commandCodeVersion = readBundledVersion ( ) ;
107+ const manifest = readBundledManifest ( ) ;
108+ if ( manifest ?. status === "degraded" || manifest ?. status === "broken" ) {
109+ degraded = true ;
110+ degradedReason =
111+ manifest . status === "broken"
112+ ? "bundled catalog marked broken"
113+ : "bundled catalog has models with no listed price" ;
114+ }
115+ } else {
116+ const cached = readCatalogCache ( ) ;
117+ if ( cached ) {
118+ models = cached ;
119+ catalogSource = "cache" ;
120+ degraded = true ;
121+ degradedReason = "bundled models.json unreadable; using last-good cache" ;
122+ } else {
123+ degraded = true ;
124+ degradedReason = "no bundled catalog and no cache" ;
125+ }
126+ }
127+ }
128+
129+ return { models, catalogSource, commandCodeVersion, degraded, degradedReason } ;
130+ }
131+
132+ function persistCatalogSideEffects ( load : CatalogLoad , debug : boolean ) : void {
133+ if ( load . models . length > 0 ) {
134+ try {
135+ writeCatalogCache ( pluginStateDir ( ) , load . models ) ;
136+ } catch {
137+ // ignore cache write
138+ }
139+ }
140+
141+ const summary = {
142+ catalogSource : load . catalogSource ,
143+ commandCodeVersion : load . commandCodeVersion ,
144+ modelCount : load . models . length ,
145+ reasoningModelCount : load . models . filter ( ( m ) => m . reasoning ) . length ,
146+ degraded : load . degraded ,
147+ degradedReason : load . degradedReason ,
148+ } ;
149+ try {
150+ writeStartupSummary ( pluginStateDir ( ) , summary ) ;
151+ } catch {
152+ // ignore
153+ }
154+ if ( debug ) {
155+ console . warn ( "[commandcode]" , JSON . stringify ( summary ) ) ;
156+ }
157+ }
158+
159+ /** V1 entrypoint (OpenCode 1.18.29+ also accepts it via `server`). */
160+ export async function server ( ) {
76161 return {
77162 config : async ( config : Record < string , unknown > ) => {
78163 if ( ! ( config as Record < string , unknown > ) . provider ) {
@@ -85,86 +170,16 @@ export default async function commandcodePlugin() {
85170
86171 const pluginCfg = loadPluginConfig ( ) ;
87172 const debug = pluginCfg . debugStartupLogs === true ;
88- const override =
89- pluginCfg . commandCodePackagePath ?. trim ( ) ||
90- process . env . COMMANDCODE_PACKAGE_PATH ?. trim ( ) ||
91- "" ;
92173
93174 if ( ! cc . npm ) cc . npm = "commandcode-go-opencode-provider" ;
94175 if ( ! cc . name ) cc . name = "Command Code" ;
95176 if ( ! cc . env ) cc . env = [ "COMMANDCODE_API_KEY" ] ;
96177
97178 if ( cc . models ) return ;
98179
99- let models : ModelEntry [ ] = [ ] ;
100- let catalogSource : "bundled" | "cache" | "opt-in-local" = "bundled" ;
101- let commandCodeVersion : string | null = null ;
102- let degraded = false ;
103- let degradedReason : string | null = null ;
104-
105- if ( override ) {
106- const localCatalog = loadCatalogFromLocalCommandCode ( { packagePath : override } ) ;
107- if ( localCatalog && localCatalog . models . length > 0 ) {
108- models = localCatalog . models ;
109- catalogSource = "opt-in-local" ;
110- commandCodeVersion = localCatalog . version ;
111- }
112- }
113-
114- if ( models . length === 0 ) {
115- const bundled = loadBundledModels ( ) ;
116- if ( bundled ) {
117- models = bundled ;
118- catalogSource = "bundled" ;
119- commandCodeVersion = readBundledVersion ( ) ;
120- const manifest = readBundledManifest ( ) ;
121- if ( manifest ?. status === "degraded" || manifest ?. status === "broken" ) {
122- degraded = true ;
123- degradedReason =
124- manifest . status === "broken"
125- ? "bundled catalog marked broken"
126- : "bundled catalog has models with no listed price" ;
127- }
128- } else {
129- const cached = readCatalogCache ( ) ;
130- if ( cached ) {
131- models = cached ;
132- catalogSource = "cache" ;
133- degraded = true ;
134- degradedReason = "bundled models.json unreadable; using last-good cache" ;
135- } else {
136- degraded = true ;
137- degradedReason = "no bundled catalog and no cache" ;
138- }
139- }
140- }
141-
142- if ( models . length > 0 ) {
143- try {
144- writeCatalogCache ( pluginStateDir ( ) , models ) ;
145- } catch {
146- // ignore cache write
147- }
148- }
149-
150- cc . models = generateOpencodeModels ( models ) ;
151-
152- const summary = {
153- catalogSource,
154- commandCodeVersion,
155- modelCount : models . length ,
156- reasoningModelCount : models . filter ( ( m ) => m . reasoning ) . length ,
157- degraded,
158- degradedReason,
159- } ;
160- try {
161- writeStartupSummary ( pluginStateDir ( ) , summary ) ;
162- } catch {
163- // ignore
164- }
165- if ( debug ) {
166- console . warn ( "[commandcode]" , JSON . stringify ( summary ) ) ;
167- }
180+ const load = loadCatalogEntries ( ) ;
181+ persistCatalogSideEffects ( load , debug ) ;
182+ cc . models = generateOpencodeModels ( load . models ) ;
168183 } ,
169184
170185 auth : {
@@ -195,3 +210,47 @@ export default async function commandcodePlugin() {
195210 } ,
196211 } ;
197212}
213+
214+ /** V2 setup: provider inventory + models through transforms. Auth stays V1-only for now. */
215+ async function setup ( ctx : any ) : Promise < void > {
216+ const pluginCfg = loadPluginConfig ( ) ;
217+ const debug = pluginCfg . debugStartupLogs === true ;
218+ const load = loadCatalogEntries ( ) ;
219+ persistCatalogSideEffects ( load , debug ) ;
220+ const v2models = generateV2Models ( load . models ) ;
221+
222+ await ctx . provider . transform ( ( editor : any ) => {
223+ const existing = editor . get ( "commandcode" ) ;
224+ if ( ! existing ) {
225+ editor . add ( {
226+ info : {
227+ id : "commandcode" ,
228+ name : "Command Code" ,
229+ activation : "enabled" ,
230+ package : "aisdk:@ai-sdk/openai-compatible" ,
231+ settings : {
232+ baseURL : "https://api.commandcode.ai/provider/v1" ,
233+ apiKey : "{env:COMMANDCODE_API_KEY}" ,
234+ } ,
235+ } ,
236+ models : v2models ,
237+ } ) ;
238+ return ;
239+ }
240+ editor . update ( "commandcode" , ( provider : any ) => {
241+ provider . name = provider . name ?? "Command Code" ;
242+ provider . settings = provider . settings ?? { } ;
243+ if ( typeof provider . settings === "object" ) {
244+ if ( ! provider . settings . baseURL )
245+ provider . settings . baseURL = "https://api.commandcode.ai/provider/v1" ;
246+ if ( ! provider . settings . apiKey ) provider . settings . apiKey = "{env:COMMANDCODE_API_KEY}" ;
247+ }
248+ } ) ;
249+ editor . models . set ( "commandcode" , v2models ) ;
250+ } ) ;
251+ }
252+
253+ const definition = { id : "commandcode" , setup } ;
254+
255+ // Dual entry: V2 reads default.id/setup, V1 (>=1.18.29) calls default.server().
256+ export default { ...definition , server } ;
0 commit comments