diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bdbe0..0c379f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ version with its date and start a fresh empty `[Unreleased]` above it. ## [Unreleased] +### Added + +- Models you configured as bring-your-own-key in qodercli — third-party + models and self-defined providers with a custom base URL — now show up in + the model selector and can be used from the composer. + ## [1.0.10] - 2026-09-16 ### Added diff --git a/src/qoder/commands/probe-runtime-commands.ts b/src/qoder/commands/probe-runtime-commands.ts index 120c8ce..e34597c 100644 --- a/src/qoder/commands/probe-runtime-commands.ts +++ b/src/qoder/commands/probe-runtime-commands.ts @@ -36,7 +36,7 @@ function mapSdkCommands(sdkCommands: SDKSlashCommand[]): SlashCommand[] { function resolveSdkModelGroup(model: ModelInfo): string { const source = model.source as string | undefined; if (model.serverScene === 'byok_enterprise' || source === 'organization') return 'Enterprise'; - if (source === 'user') return 'Custom'; + if (source === 'user' || source === 'custom') return 'Custom'; if (model.isNew) return 'New models'; return 'Qoder'; } @@ -376,6 +376,8 @@ export async function probeRuntimeCatalog( const commands = mapSdkCommands(initialization.commands ?? []); const agents = mapSdkAgents(initialization.agents ?? []); + // CLI-configured custom providers already ship with the CLI's model list; + // asking the server for them by id fails the live fetch outright. let models: QoderDiscoveredModel[] = []; try { const liveModels: ModelInfo[] = await conversation.getAvailableModels({ fetchStrategy: 'live' }); diff --git a/src/qoder/runtime/custom-spawn.ts b/src/qoder/runtime/custom-spawn.ts index 7d49b37..0d26ccc 100644 --- a/src/qoder/runtime/custom-spawn.ts +++ b/src/qoder/runtime/custom-spawn.ts @@ -8,6 +8,13 @@ import { type WindowsCmdShimSpawnSpec, } from './windows-cmd-shim'; +/** + * SDK-spawned sessions run qodercli in its "sdk" runtime mode, where + * CLI-configured custom providers (self-defined base URLs) stay disabled + * unless this opt-in is present — without it the CLI hides their models. + */ +const CUSTOM_PROVIDER_CLI_ENV = { QODER_SDK_CUSTOM_BASE_URL_BYOK: '1' } as const; + /** * Spawn qodercli in Obsidian's Electron process. * @@ -36,7 +43,7 @@ export function createCustomSpawnFunction( const resolvedSpawnSpec = resolveWindowsCmdShimSpawnSpec({ args, command }); const child = spawn(resolvedSpawnSpec.command, resolvedSpawnSpec.args, { cwd, - env, + env: env ? { ...env, ...CUSTOM_PROVIDER_CLI_ENV } : env, // stderr is always piped so host code (e.g. the runtime probe) can read // CLI diagnostics such as "No qodercli login found". A drain listener is // attached below to avoid pipe backpressure when nobody else listens. diff --git a/tests/unit/qoder/commands/probe-runtime-commands.test.ts b/tests/unit/qoder/commands/probe-runtime-commands.test.ts index 9e0c0bd..e24f84d 100644 --- a/tests/unit/qoder/commands/probe-runtime-commands.test.ts +++ b/tests/unit/qoder/commands/probe-runtime-commands.test.ts @@ -349,4 +349,19 @@ describe('probeRuntimeCatalog', () => { fetchStrategy: 'live', }); }); + it('groups custom-provider models under Custom', async () => { + setInitMessage({}); + sdkMock.setMockAvailableModels([ + { value: 'gw-gpt', displayName: 'GW GPT', source: 'custom', isEnabled: true }, + { value: 'auto', displayName: 'Auto', source: 'system', isEnabled: true }, + ]); + + const result = await probeRuntimeCatalog(createMockPlugin()); + + if ('error' in result) throw new Error('probe failed'); + expect(result.models.map((model) => [model.value, model.group])).toEqual([ + ['gw-gpt', 'Custom'], + ['auto', 'Qoder'], + ]); + }); }); diff --git a/tests/unit/qoder/runtime/custom-spawn.test.ts b/tests/unit/qoder/runtime/custom-spawn.test.ts index 06b1fd0..318f0e7 100644 --- a/tests/unit/qoder/runtime/custom-spawn.test.ts +++ b/tests/unit/qoder/runtime/custom-spawn.test.ts @@ -108,6 +108,26 @@ describe('createCustomSpawnFunction', () => { ); }); + it('opts every spawned CLI into custom providers', () => { + const mockProcess = createMockProcess(); + spawnMock.mockReturnValue(mockProcess as unknown as ReturnType); + + const spawnFn = createCustomSpawnFunction('/enhanced/path'); + spawnFn({ + command: 'node', + args: ['cli.js'], + cwd: '/tmp', + env: { PATH: '/usr/bin' }, + signal: new AbortController().signal, + }); + + const spawnOptions = spawnMock.mock.calls[0][2]; + expect(spawnOptions.env).toEqual({ + PATH: '/usr/bin', + QODER_SDK_CUSTOM_BASE_URL_BYOK: '1', + }); + }); + it('always pipes stderr so host code can read CLI diagnostics', () => { const mockProcess = createMockProcess(); spawnMock.mockReturnValue(mockProcess as unknown as ReturnType);