agent-ecosystem/test/main/services/runtime/ProviderConnectionService.test.ts
Jan cc10485f0c
feat(kilocode): add provider support
* Add KiloCode as a first-class provider with HTTP-based model catalog

Implements KiloCode (kilo.ai gateway) support following repo design principles,
independently of the OpenCode implementation.

Key changes:
- Add 'kilocode' to CliProviderId, TeamProviderId, MemberWorkSyncProviderId
- Create kilocode-model-catalog feature: HTTP client fetching models from
  kilo.ai /models endpoint (not /v1/models — different gateway path)
- Add KILO_API_KEY env var for authentication
- Wire kilocode into provider routing, capabilities, and UI labels
- Add 'kilo' brand icon alias in providerBrandIcons (auto-fetches from models.dev)
- KiloCode status is managed via the HTTP gateway, not the multimodel bridge

* Fix: preserve non-bridge providers (kilocode) when updating provider status

The multimodel bridge only returns status for anthropic/codex/gemini/opencode.
When checkAuthStatus replaced result.providers with the bridge response,
kilocode was lost from the provider list and never appeared in the UI.

Now merge bridge providers with the initial list, keeping any provider
not covered by the bridge so kilocode shows up in the Extensions panel.

* Fix: resolve KiloCode status after bridge merge, skip bridge refresh for non-bridge providers

- resolveKilocodeStatus() gives kilocode a settled verificationState:'verified' status
  so isHydratedMultimodelProviderStatus() returns true and the loading spinner stops
- Status reflects KILO_API_KEY presence: authenticated+supported when set, else clear message
- fetchCliStatus() now skips fetchCliProviderStatus for non-bridge providers (kilocode)
  so the Claude Code CLI is not queried for kilocode, preventing error status overwrites

* Add KiloCode to API key provider system in settings dialog

isApiKeyProviderId now includes kilocode, so the API key form renders
in the Provider Settings dialog instead of showing an empty modal.
Adds KILO_API_KEY config with placeholder and description.

* Fix KiloCode models endpoint: /api/gateway/models per docs

* Fix: short-circuit getProviderStatus/verifyProviderModels for kilocode

The Claude Code CLI only accepts anthropic and codex for --provider.
Calling it with kilocode caused the blinking modal error.

resolveKilocodeProviderStatus() returns status directly from env
without touching the CLI binary — no bridge, no --provider flag.

* Fix: resolveKilocodeProviderStatus reads from app key store via enrichProviderStatus

process.env.KILO_API_KEY was only set for users who configured it in their
shell environment. The UI stores the key in the app's encrypted key store
(ApiKeyService), which enrichProviderStatus checks via hasStoredProviderApiKey.

Now resolveKilocodeProviderStatus() calls providerConnectionService.enrichProviderStatus()
so both the app key store and env var are checked — the same way anthropic/gemini work.

* Wire KiloCode model catalog into provider status — models now load from gateway

- ProviderConnectionService: add setKilocodeModelCatalogFeature() and
  enrichKilocodeProviderStatus() which fetches models from the gateway API
  and populates provider.models when the API key is configured
- main/index.ts: create KilocodeModelCatalogFeature at startup and inject
  it into ProviderConnectionService, same lifecycle as Codex catalog

* Fix: skip Claude CLI probe for kilocode in prepareForProvisioning

The generic probe path calls probeClaudeRuntime with CLAUDE_CODE_ENTRY_PROVIDER=kilocode
which causes the CLI to hang — freezing the Create Team dialog until timeout.

Add an explicit kilocode case that short-circuits to an API key presence check
(via providerConnectionService.getConnectionInfo) without touching the Claude binary,
same pattern as the opencode adapter bypass.

* Fix vitest localStorage fallback

* test(kilocode): update provider visibility expectations

---------

Co-authored-by: 777genius <quantjumppro@gmail.com>
2026-05-25 21:12:37 +03:00

2520 lines
76 KiB
TypeScript

// @vitest-environment node
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { CodexAccountSnapshotDto } from '@features/codex-account/contracts';
const getCachedShellEnvMock = vi.fn<() => NodeJS.ProcessEnv | null>();
const execCliMock = vi.fn<
(
binaryPath: string | null,
args: string[],
options?: {
env?: NodeJS.ProcessEnv;
timeout?: number;
windowsHide?: boolean;
maxBuffer?: number;
}
) => Promise<{ stdout: string; stderr: string }>
>();
vi.mock('@main/utils/shellEnv', () => ({
getCachedShellEnv: () => getCachedShellEnvMock(),
}));
vi.mock('@main/utils/childProcess', () => ({
execCli: (
binaryPath: string | null,
args: string[],
options?: {
env?: NodeJS.ProcessEnv;
timeout?: number;
windowsHide?: boolean;
maxBuffer?: number;
}
) => execCliMock(binaryPath, args, options),
}));
describe('ProviderConnectionService', () => {
const originalOpenAiApiKey = process.env.OPENAI_API_KEY;
const originalCodexApiKey = process.env.CODEX_API_KEY;
const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
const originalAnthropicAuthToken = process.env.ANTHROPIC_AUTH_TOKEN;
const originalAnthropicBaseUrl = process.env.ANTHROPIC_BASE_URL;
const originalKiloApiKey = process.env.KILO_API_KEY;
function createConfig(
authMode: 'auto' | 'oauth' | 'api_key' = 'auto',
compatibleEndpoint: { enabled: boolean; baseUrl: string } = { enabled: false, baseUrl: '' }
) {
return {
providerConnections: {
anthropic: {
authMode,
fastModeDefault: false,
compatibleEndpoint,
},
codex: {
preferredAuthMode: 'auto' as const,
},
},
runtime: {
providerBackends: {
gemini: 'auto' as const,
codex: 'codex-native' as const,
},
},
};
}
function createCodexSnapshot(
overrides: Partial<CodexAccountSnapshotDto> = {}
): CodexAccountSnapshotDto {
return {
preferredAuthMode: 'auto',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_chatgpt',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: false,
localAccountArtifactsPresent: true,
localActiveChatgptAccountPresent: true,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex',
codexHome: '/Users/tester/.codex-custom',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
...overrides,
};
}
function createCodexRuntimeMissingSnapshot(
overrides: Partial<CodexAccountSnapshotDto> = {}
): CodexAccountSnapshotDto {
return createCodexSnapshot({
effectiveAuthMode: null,
launchAllowed: false,
launchIssueMessage: 'Codex CLI not found',
launchReadinessState: 'runtime_missing',
appServerState: 'runtime-missing',
appServerStatusMessage: 'Codex CLI not found',
managedAccount: null,
requiresOpenaiAuth: null,
localAccountArtifactsPresent: false,
localActiveChatgptAccountPresent: false,
runtimeContext: {
binaryPath: null,
codexHome: null,
},
...overrides,
});
}
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
getCachedShellEnvMock.mockReturnValue({});
execCliMock.mockResolvedValue({ stdout: 'Logged in using ChatGPT', stderr: '' });
delete process.env.OPENAI_API_KEY;
delete process.env.CODEX_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_AUTH_TOKEN;
delete process.env.ANTHROPIC_BASE_URL;
delete process.env.KILO_API_KEY;
});
afterEach(() => {
if (originalOpenAiApiKey === undefined) {
delete process.env.OPENAI_API_KEY;
} else {
process.env.OPENAI_API_KEY = originalOpenAiApiKey;
}
if (originalCodexApiKey === undefined) {
delete process.env.CODEX_API_KEY;
} else {
process.env.CODEX_API_KEY = originalCodexApiKey;
}
if (originalAnthropicApiKey === undefined) {
delete process.env.ANTHROPIC_API_KEY;
} else {
process.env.ANTHROPIC_API_KEY = originalAnthropicApiKey;
}
if (originalAnthropicAuthToken === undefined) {
delete process.env.ANTHROPIC_AUTH_TOKEN;
} else {
process.env.ANTHROPIC_AUTH_TOKEN = originalAnthropicAuthToken;
}
if (originalAnthropicBaseUrl === undefined) {
delete process.env.ANTHROPIC_BASE_URL;
} else {
process.env.ANTHROPIC_BASE_URL = originalAnthropicBaseUrl;
}
if (originalKiloApiKey === undefined) {
delete process.env.KILO_API_KEY;
} else {
process.env.KILO_API_KEY = originalKiloApiKey;
}
});
it('removes Anthropic environment credentials when OAuth mode is selected', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('oauth'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_API_KEY: 'direct-key',
ANTHROPIC_AUTH_TOKEN: 'proxy-token',
},
'anthropic'
);
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
});
it('preserves Anthropic-compatible bearer token env even when OAuth mode is selected', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('oauth'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_BASE_URL: 'http://localhost:11434',
ANTHROPIC_API_KEY: '',
ANTHROPIC_AUTH_TOKEN: 'ollama',
},
'anthropic'
);
expect(result.ANTHROPIC_BASE_URL).toBe('http://localhost:11434');
expect(result.ANTHROPIC_API_KEY).toBe('');
expect(result.ANTHROPIC_AUTH_TOKEN).toBe('ollama');
});
it('does not treat first-party Anthropic base URLs as compatible OAuth env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('oauth'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_BASE_URL: 'HTTPS://API.ANTHROPIC.COM/v1',
ANTHROPIC_AUTH_TOKEN: 'stale-first-party-token',
},
'anthropic'
);
expect(result.ANTHROPIC_BASE_URL).toBe('HTTPS://API.ANTHROPIC.COM/v1');
expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});
it('does not preserve malformed Anthropic-compatible shell env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('oauth'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_BASE_URL: 'not a url',
ANTHROPIC_AUTH_TOKEN: 'local-token',
},
'anthropic'
);
expect(result.ANTHROPIC_BASE_URL).toBe('not a url');
expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});
it('injects the stored Anthropic API key when api_key mode is selected', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_API_KEY: undefined,
ANTHROPIC_AUTH_TOKEN: 'proxy-token',
},
'anthropic'
);
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY');
expect(result.ANTHROPIC_API_KEY).toBe('stored-key');
expect(result.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
});
it('does not replace Anthropic-compatible bearer token env with stored API key mode credentials', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_BASE_URL: 'http://localhost:11434',
ANTHROPIC_API_KEY: '',
ANTHROPIC_AUTH_TOKEN: 'ollama',
},
'anthropic'
);
expect(lookupPreferred).not.toHaveBeenCalled();
expect(result.ANTHROPIC_API_KEY).toBe('');
expect(result.ANTHROPIC_AUTH_TOKEN).toBe('ollama');
});
it('injects app-managed Anthropic-compatible endpoint env without stored Anthropic API key', async () => {
const lookupPreferred = vi.fn(async (envVarName: string) => {
if (envVarName === 'ANTHROPIC_AUTH_TOKEN') {
return {
envVarName,
value: 'stored-local-token',
};
}
if (envVarName === 'ANTHROPIC_API_KEY') {
return {
envVarName,
value: 'stored-real-anthropic-key',
};
}
return null;
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () =>
createConfig('api_key', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'anthropic');
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_AUTH_TOKEN');
expect(lookupPreferred).not.toHaveBeenCalledWith('ANTHROPIC_API_KEY');
expect(result.ANTHROPIC_BASE_URL).toBe('http://localhost:1234');
expect(result.ANTHROPIC_AUTH_TOKEN).toBe('stored-local-token');
expect(result.ANTHROPIC_API_KEY).toBe('');
});
it('uses shell ANTHROPIC_AUTH_TOKEN for app-managed compatible endpoint when no stored token exists', async () => {
getCachedShellEnvMock.mockReturnValue({
ANTHROPIC_AUTH_TOKEN: 'shell-local-token',
});
const lookupPreferred = vi.fn().mockResolvedValue(null);
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () =>
createConfig('oauth', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'anthropic');
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_AUTH_TOKEN');
expect(result.ANTHROPIC_BASE_URL).toBe('http://localhost:1234');
expect(result.ANTHROPIC_AUTH_TOKEN).toBe('shell-local-token');
expect(result.ANTHROPIC_API_KEY).toBe('');
});
it('can decrypt only the stored Anthropic-compatible token for metadata-only runtime status', async () => {
const lookupPreferred = vi.fn(async (envVarName: string) => {
if (envVarName === 'ANTHROPIC_AUTH_TOKEN') {
return {
envVarName,
value: 'stored-local-token',
};
}
if (envVarName === 'ANTHROPIC_API_KEY') {
return {
envVarName,
value: 'stored-real-anthropic-key',
};
}
return null;
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () =>
createConfig('api_key', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'anthropic', undefined, {
allowStoredApiKeyDecryption: false,
allowedStoredApiKeyEnvVarNames: ['ANTHROPIC_AUTH_TOKEN'],
});
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_AUTH_TOKEN');
expect(lookupPreferred).not.toHaveBeenCalledWith('ANTHROPIC_API_KEY');
expect(result.ANTHROPIC_BASE_URL).toBe('http://localhost:1234');
expect(result.ANTHROPIC_AUTH_TOKEN).toBe('stored-local-token');
expect(result.ANTHROPIC_API_KEY).toBe('');
});
it('preserves explicit env ANTHROPIC_API_KEY for an app-managed compatible endpoint', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () =>
createConfig('auto', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
ANTHROPIC_API_KEY: 'explicit-local-token',
},
'anthropic'
);
expect(result.ANTHROPIC_BASE_URL).toBe('http://localhost:1234');
expect(result.ANTHROPIC_API_KEY).toBe('explicit-local-token');
});
it('does not require an Anthropic API key when app-managed compatible endpoint is enabled', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () =>
createConfig('api_key', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const issue = await service.getConfiguredConnectionIssue({}, 'anthropic');
expect(issue).toBeNull();
});
it('reports invalid app-managed compatible endpoint URLs before mutating Anthropic env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () =>
createConfig('auto', {
enabled: true,
baseUrl: 'http://token@localhost:1234',
}),
} as never
);
await expect(service.getConfiguredConnectionIssue({}, 'anthropic')).resolves.toContain(
'must not include credentials'
);
const env = await service.applyConfiguredConnectionEnv({}, 'anthropic');
expect(env.ANTHROPIC_BASE_URL).toBeUndefined();
expect(env.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
expect(env.ANTHROPIC_API_KEY).toBeUndefined();
});
it('reports app-managed Anthropic-compatible token source without decrypting it', async () => {
const lookupPreferred = vi.fn().mockResolvedValue(null);
const hasPreferred = vi.fn(async (envVarName: string) => envVarName === 'ANTHROPIC_AUTH_TOKEN');
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
hasPreferred,
} as never,
{
getConfig: () =>
createConfig('auto', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const info = await service.getConnectionInfo('anthropic');
expect(info.compatibleEndpoint).toEqual({
enabled: true,
baseUrl: 'http://localhost:1234',
tokenConfigured: true,
tokenSource: 'stored',
tokenSourceLabel: 'Stored in app',
});
expect(lookupPreferred).not.toHaveBeenCalled();
});
it('reports environment Anthropic-compatible token source when no stored token exists', async () => {
getCachedShellEnvMock.mockReturnValue({
ANTHROPIC_AUTH_TOKEN: 'env-local-token',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
hasPreferred: vi.fn().mockResolvedValue(false),
} as never,
{
getConfig: () =>
createConfig('auto', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
const info = await service.getConnectionInfo('anthropic');
expect(info.compatibleEndpoint).toMatchObject({
enabled: true,
baseUrl: 'http://localhost:1234',
tokenConfigured: true,
tokenSource: 'environment',
tokenSourceLabel: 'Detected from ANTHROPIC_AUTH_TOKEN',
});
});
it('does not decrypt stored Anthropic keys when metadata-only env building is requested', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'anthropic', undefined, {
allowStoredApiKeyDecryption: false,
});
expect(lookupPreferred).not.toHaveBeenCalled();
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});
it('injects stored Gemini API keys for runtime launches', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'GEMINI_API_KEY',
value: 'gemini-stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'gemini');
expect(lookupPreferred).toHaveBeenCalledWith('GEMINI_API_KEY');
expect(result.GEMINI_API_KEY).toBe('gemini-stored-key');
});
it('injects stored KiloCode API keys for runtime launches', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'KILO_API_KEY',
value: 'kilo-stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'kilocode');
expect(lookupPreferred).toHaveBeenCalledWith('KILO_API_KEY');
expect(result.KILO_API_KEY).toBe('kilo-stored-key');
await expect(service.getConfiguredConnectionIssue(result, 'kilocode')).resolves.toBeNull();
});
it('reports a missing KiloCode API key before runtime launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const issue = await service.getConfiguredConnectionIssue({}, 'kilocode');
expect(issue).toContain('KiloCode API key is not configured');
});
it('passes stored KiloCode API keys to catalog hydration', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'KILO_API_KEY',
value: 'kilo-stored-key',
});
const getCatalog = vi.fn().mockResolvedValue({
schemaVersion: 1,
providerId: 'kilocode',
source: 'app-server',
status: 'ready',
fetchedAt: '2026-05-25T00:00:00.000Z',
staleAt: '2026-05-25T00:10:00.000Z',
defaultModelId: 'kilo/test',
defaultLaunchModel: 'kilo/test',
models: [
{
id: 'kilo/test',
launchModel: 'kilo/test',
displayName: 'Kilo Test',
hidden: false,
supportedReasoningEfforts: [],
defaultReasoningEffort: null,
inputModalities: ['text'],
supportsPersonality: false,
isDefault: true,
upgrade: false,
source: 'app-server',
},
],
diagnostics: {
configReadState: 'skipped',
appServerState: 'healthy',
message: null,
code: null,
},
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setKilocodeModelCatalogFeature({ getCatalog });
const enriched = await service.enrichProviderStatus({
providerId: 'kilocode',
displayName: 'KiloCode',
supported: true,
authenticated: true,
authMethod: 'api_key',
verificationState: 'verified',
modelVerificationState: 'idle',
statusMessage: null,
models: [],
modelAvailability: [],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: false,
extensions: {
plugins: { supported: false, status: 'unsupported' },
mcp: { supported: false, status: 'unsupported' },
skills: { supported: false, status: 'unsupported' },
apiKeys: { supported: true, status: 'supported' },
},
},
backend: null,
} as never);
expect(getCatalog).toHaveBeenCalledWith({ apiKey: 'kilo-stored-key' });
expect(enriched.models).toEqual(['kilo/test']);
});
it('reports a missing Anthropic API key when api_key mode is selected', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const issue = await service.getConfiguredConnectionIssue({}, 'anthropic');
expect(issue).toContain('Anthropic API key mode is enabled');
expect(issue).toContain('ANTHROPIC_API_KEY');
});
it('does not report a missing Anthropic API key for Anthropic-compatible bearer token env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const issue = await service.getConfiguredConnectionIssue(
{
ANTHROPIC_BASE_URL: 'http://localhost:11434',
ANTHROPIC_API_KEY: '',
ANTHROPIC_AUTH_TOKEN: 'ollama',
},
'anthropic'
);
expect(issue).toBeNull();
});
it('treats a stored Anthropic API key as configured even when env is empty', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const issue = await service.getConfiguredConnectionIssue({}, 'anthropic');
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY');
expect(issue).toBeNull();
});
it('can swap to the shared API key service after construction', async () => {
const staleApiKeyService = {
lookupPreferred: vi.fn().mockResolvedValue(null),
};
const sharedApiKeyService = {
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'shared-key',
}),
};
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
staleApiKeyService as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
expect(await service.getConfiguredConnectionIssue({}, 'anthropic')).toContain(
'Anthropic API key mode is enabled'
);
service.setApiKeyService(sharedApiKeyService as never);
expect(await service.getConfiguredConnectionIssue({}, 'anthropic')).toBeNull();
expect(sharedApiKeyService.lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY');
});
it('prefers stored API key status over environment detection for Anthropic', async () => {
getCachedShellEnvMock.mockReturnValue({
ANTHROPIC_API_KEY: 'shell-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const info = await service.getConnectionInfo('anthropic');
expect(info).toMatchObject({
supportsOAuth: true,
supportsApiKey: true,
configuredAuthMode: 'auto',
apiKeyConfigured: true,
apiKeySource: 'stored',
apiKeySourceLabel: 'Stored in app',
});
});
it('does not report stored Anthropic API key mode as connected until runtime verifies the API key', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const verifyAnthropicApiKey = vi.fn().mockResolvedValue({ state: 'unknown' });
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never,
undefined,
verifyAnthropicApiKey
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'claude.ai',
verificationState: 'verified',
statusMessage: 'Connected via Anthropic subscription',
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: false,
authMethod: null,
verificationState: 'unknown',
statusMessage:
'Anthropic API key is configured, but has not been verified by the runtime yet.',
connection: {
configuredAuthMode: 'api_key',
apiKeyConfigured: true,
apiKeySource: 'stored',
apiKeySourceLabel: 'Stored in app',
},
});
expect(verifyAnthropicApiKey).toHaveBeenCalledWith('stored-key', null);
});
it('reports Anthropic API key mode as connected after direct API verification succeeds', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const verifyAnthropicApiKey = vi.fn().mockResolvedValue({ state: 'valid', status: 200 });
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never,
undefined,
verifyAnthropicApiKey
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'claude.ai',
verificationState: 'verified',
statusMessage: 'Connected via Anthropic subscription',
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: true,
authMethod: 'api_key',
verificationState: 'verified',
statusMessage: 'Connected via API key',
connection: {
configuredAuthMode: 'api_key',
apiKeyConfigured: true,
apiKeySource: 'stored',
apiKeySourceLabel: 'Stored in app',
},
});
expect(verifyAnthropicApiKey).toHaveBeenCalledTimes(1);
});
it('verifies Anthropic API keys against ANTHROPIC_BASE_URL when configured', async () => {
getCachedShellEnvMock.mockReturnValue({
ANTHROPIC_BASE_URL: 'https://gateway.example/anthropic/',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const fetchMock = vi.fn(async () => new Response('{}', { status: 200 }));
vi.stubGlobal('fetch', fetchMock);
try {
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'claude.ai',
verificationState: 'verified',
statusMessage: 'Connected via Anthropic subscription',
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(fetchMock).toHaveBeenCalledWith(
'https://gateway.example/anthropic/v1/models',
expect.objectContaining({
headers: expect.objectContaining({
'x-api-key': 'stored-key',
'anthropic-version': '2023-06-01',
}),
method: 'GET',
})
);
} finally {
vi.unstubAllGlobals();
}
});
it('reports an invalid Anthropic API key after direct API verification fails', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const verifyAnthropicApiKey = vi.fn().mockResolvedValue({
state: 'invalid',
status: 401,
errorType: 'authentication_error',
errorMessage: 'invalid x-api-key',
});
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never,
undefined,
verifyAnthropicApiKey
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'claude.ai',
verificationState: 'verified',
statusMessage: 'Connected via Anthropic subscription',
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: false,
authMethod: null,
verificationState: 'error',
statusMessage: 'Anthropic API key verification failed: invalid x-api-key',
});
});
it('reports Anthropic API key mode as connected after runtime verifies the API key', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'api_key',
verificationState: 'verified',
statusMessage: null,
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: true,
authMethod: 'api_key',
verificationState: 'verified',
statusMessage: 'Connected via API key',
connection: {
configuredAuthMode: 'api_key',
apiKeyConfigured: true,
apiKeySource: 'stored',
apiKeySourceLabel: 'Stored in app',
},
});
});
it('treats Anthropic API-key helper runtime auth as verified API-key mode', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const verifyAnthropicApiKey = vi.fn().mockResolvedValue({ state: 'unknown' });
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never,
undefined,
verifyAnthropicApiKey
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'api_key_helper',
verificationState: 'verified',
statusMessage: null,
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: true,
authMethod: 'api_key_helper',
verificationState: 'verified',
statusMessage: 'Connected via API key',
});
expect(verifyAnthropicApiKey).not.toHaveBeenCalled();
});
it('does not treat a subscription session as connected when Anthropic API key mode has no key', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
const status = await service.enrichProviderStatus({
providerId: 'anthropic',
displayName: 'Anthropic',
supported: true,
authenticated: true,
authMethod: 'claude.ai',
verificationState: 'verified',
statusMessage: 'Connected via Anthropic subscription',
models: ['claude-sonnet-4-6'],
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: { mcp: 'unsupported', skills: 'unsupported', plugins: 'unsupported' },
},
selectedBackendId: null,
resolvedBackendId: null,
availableBackends: [],
externalRuntimeDiagnostics: [],
backend: null,
connection: null,
} as never);
expect(status).toMatchObject({
authenticated: false,
authMethod: null,
verificationState: 'unknown',
statusMessage: 'API key mode is selected, but no Anthropic API credential is available yet.',
connection: {
configuredAuthMode: 'api_key',
apiKeyConfigured: false,
},
});
});
it('exposes Codex as native-only API-key runtime', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const info = await service.getConnectionInfo('codex');
expect(info).toMatchObject({
supportsOAuth: false,
supportsApiKey: true,
configurableAuthModes: ['auto', 'chatgpt', 'api_key'],
configuredAuthMode: 'auto',
apiKeyConfigured: false,
apiKeySource: null,
apiKeySourceLabel: null,
});
});
it('mirrors a stored OpenAI key into CODEX_API_KEY for native Codex launches', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'OPENAI_API_KEY',
value: 'openai-stored-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const result = await service.applyConfiguredConnectionEnv({}, 'codex');
expect(lookupPreferred).toHaveBeenCalledWith('OPENAI_API_KEY');
expect(result.OPENAI_API_KEY).toBe('openai-stored-key');
expect(result.CODEX_API_KEY).toBe('openai-stored-key');
});
it('keeps ambient OpenAI credentials for native Codex launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const result = await service.applyConfiguredConnectionEnv(
{
OPENAI_API_KEY: 'shell-openai-key',
},
'codex'
);
expect(result.OPENAI_API_KEY).toBe('shell-openai-key');
expect(result.CODEX_API_KEY).toBe('shell-openai-key');
});
it('passes Codex runtime context while clearing API keys for ChatGPT launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_chatgpt',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: false,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex',
codexHome: '/Users/tester/.codex-custom',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const result = await service.applyConfiguredConnectionEnv(
{
OPENAI_API_KEY: 'ambient-openai-key',
CODEX_API_KEY: 'ambient-codex-key',
},
'codex'
);
expect(result.OPENAI_API_KEY).toBeUndefined();
expect(result.CODEX_API_KEY).toBeUndefined();
expect(result.CODEX_CLI_PATH).toBe('/opt/codex/bin/codex');
expect(result.CODEX_HOME).toBe('/Users/tester/.codex-custom');
});
it('keeps Codex runtime context when API-key mode mirrors credentials', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'OPENAI_API_KEY',
value: 'stored-openai-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'api_key',
effectiveAuthMode: 'api_key',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_api_key',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: null,
apiKey: {
available: true,
source: 'stored',
sourceLabel: 'Stored in app',
},
requiresOpenaiAuth: false,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex.cmd',
codexHome: 'C:\\Users\\tester\\.codex',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const result = await service.applyConfiguredConnectionEnv({}, 'codex');
expect(result.OPENAI_API_KEY).toBe('stored-openai-key');
expect(result.CODEX_API_KEY).toBe('stored-openai-key');
expect(result.CODEX_CLI_PATH).toBe('/opt/codex/bin/codex.cmd');
expect(result.CODEX_HOME).toBe('C:\\Users\\tester\\.codex');
});
it('accepts CODEX_API_KEY as the native external credential source for Codex', async () => {
getCachedShellEnvMock.mockReturnValue({
CODEX_API_KEY: 'native-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const info = await service.getConnectionInfo('codex');
const issue = await service.getConfiguredConnectionIssue(
{
CODEX_API_KEY: 'native-key',
},
'codex'
);
expect(info.apiKeyConfigured).toBe(true);
expect(info.apiKeySource).toBe('environment');
expect(info.apiKeySourceLabel).toBe('Detected from CODEX_API_KEY');
expect(issue).toBeNull();
});
it('reports a missing native Codex credential when neither OPENAI_API_KEY nor CODEX_API_KEY exist', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const issue = await service.getConfiguredConnectionIssue({}, 'codex');
expect(issue).toContain('Codex native requires OPENAI_API_KEY or CODEX_API_KEY');
});
it('refreshes a runtime-missing Codex snapshot before blocking launch preflight', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const runtimeMissingSnapshot = createCodexRuntimeMissingSnapshot();
const refreshSnapshot = vi.fn().mockResolvedValue(
createCodexSnapshot({
effectiveAuthMode: 'api_key',
launchReadinessState: 'ready_api_key',
managedAccount: null,
})
);
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue(runtimeMissingSnapshot),
refreshSnapshot,
});
const issue = await service.getConfiguredConnectionIssue(
{
CODEX_API_KEY: 'native-key',
},
'codex'
);
expect(issue).toBeNull();
expect(refreshSnapshot).toHaveBeenCalledWith({ forceRefreshToken: true });
});
it('refreshes a runtime-missing Codex snapshot before mutating strict launch env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const refreshSnapshot = vi.fn().mockResolvedValue(createCodexSnapshot());
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue(createCodexRuntimeMissingSnapshot()),
refreshSnapshot,
});
const env = await service.applyConfiguredConnectionEnv(
{
OPENAI_API_KEY: 'ambient-openai-key',
CODEX_API_KEY: 'ambient-codex-key',
},
'codex'
);
expect(env.OPENAI_API_KEY).toBeUndefined();
expect(env.CODEX_API_KEY).toBeUndefined();
expect(env.CODEX_CLI_PATH).toBe('/opt/codex/bin/codex');
expect(env.CODEX_HOME).toBe('/Users/tester/.codex-custom');
expect(env.CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD).toBe('chatgpt');
expect(refreshSnapshot).toHaveBeenCalledWith({ forceRefreshToken: true });
});
it('refreshes a runtime-missing Codex snapshot before augmenting API-key launch env', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const refreshSnapshot = vi.fn().mockResolvedValue(
createCodexSnapshot({
preferredAuthMode: 'api_key',
effectiveAuthMode: 'api_key',
launchReadinessState: 'ready_api_key',
managedAccount: null,
apiKey: {
available: true,
source: 'environment',
sourceLabel: 'Detected from CODEX_API_KEY',
},
})
);
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue(createCodexRuntimeMissingSnapshot()),
refreshSnapshot,
});
const env = await service.augmentConfiguredConnectionEnv(
{
CODEX_API_KEY: 'native-key',
},
'codex'
);
expect(env.OPENAI_API_KEY).toBe('native-key');
expect(env.CODEX_API_KEY).toBe('native-key');
expect(env.CODEX_CLI_PATH).toBe('/opt/codex/bin/codex');
expect(env.CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD).toBe('api');
expect(refreshSnapshot).toHaveBeenCalledWith({ forceRefreshToken: true });
});
it('keeps the original runtime-missing issue when the forced Codex snapshot refresh fails', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue(createCodexRuntimeMissingSnapshot()),
refreshSnapshot: vi.fn().mockRejectedValue(new Error('refresh failed')),
});
const issue = await service.getConfiguredConnectionIssue({}, 'codex');
expect(issue).toBe('Codex CLI not found');
});
it('refreshes a runtime-missing Codex snapshot before building forced launch args', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue(createCodexRuntimeMissingSnapshot()),
refreshSnapshot: vi.fn().mockResolvedValue(createCodexSnapshot()),
});
const args = await service.getConfiguredConnectionLaunchArgs(
{},
'codex',
'codex-native',
'codex'
);
expect(args).toEqual(['-c', 'forced_login_method="chatgpt"']);
});
it('reports a pinned Codex ChatGPT mode as a missing active CLI login instead of flattening it to generic auth advice', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: null,
launchAllowed: false,
launchIssueMessage: 'Connect a ChatGPT account to use your Codex subscription.',
launchReadinessState: 'missing_auth',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: null,
apiKey: {
available: true,
source: 'environment',
sourceLabel: 'Detected from OPENAI_API_KEY',
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: false,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const issue = await service.getConfiguredConnectionIssue(
{
OPENAI_API_KEY: 'env-key',
},
'codex'
);
expect(issue).toBe(
'Codex ChatGPT account mode is selected, but Codex CLI reports no active ChatGPT login. Connect ChatGPT again or switch Codex auth mode to API key.'
);
});
it('mentions local Codex account artifacts when pinned ChatGPT mode has no active managed session', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: null,
launchAllowed: false,
launchIssueMessage: 'Connect a ChatGPT account to use your Codex subscription.',
launchReadinessState: 'missing_auth',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: null,
apiKey: {
available: true,
source: 'environment',
sourceLabel: 'Detected from OPENAI_API_KEY',
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: true,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const issue = await service.getConfiguredConnectionIssue(
{
OPENAI_API_KEY: 'env-key',
},
'codex'
);
expect(issue).toBe(
'Codex ChatGPT account mode is selected, but Codex CLI reports no active ChatGPT login. Local Codex account data exists, but no active managed session is selected. Connect ChatGPT again or switch Codex auth mode to API key.'
);
});
it('asks for reconnect when pinned ChatGPT mode still has a locally selected Codex account', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: null,
launchAllowed: false,
launchIssueMessage: 'Reconnect ChatGPT to refresh the current Codex subscription session.',
launchReadinessState: 'missing_auth',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: null,
apiKey: {
available: true,
source: 'environment',
sourceLabel: 'Detected from OPENAI_API_KEY',
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: true,
localActiveChatgptAccountPresent: true,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const issue = await service.getConfiguredConnectionIssue(
{
OPENAI_API_KEY: 'env-key',
},
'codex'
);
expect(issue).toBe(
'Codex ChatGPT account mode is selected, and Codex has a locally selected ChatGPT account, but the current session needs reconnect. Reconnect ChatGPT or switch Codex auth mode to API key.'
);
});
it('does not block launch when the Codex app-server freshly verifies ChatGPT auth', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const loginStatusChecker = vi.fn().mockResolvedValue({
status: 'not_logged_in',
detail: 'Not logged in',
});
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never,
loginStatusChecker
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_chatgpt',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: true,
localActiveChatgptAccountPresent: true,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex',
codexHome: '/Users/tester/.codex-custom',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
await expect(
service.getConfiguredConnectionIssue(
{
OPENAI_API_KEY: 'ambient-openai-key',
CODEX_API_KEY: 'ambient-codex-key',
},
'codex'
)
).resolves.toBeNull();
expect(loginStatusChecker).not.toHaveBeenCalled();
});
it('verifies degraded Codex cmd shim login status through the shared CLI launcher', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'warning_degraded_but_launchable',
appServerState: 'degraded',
appServerStatusMessage: 'Using cached ChatGPT account after transient app-server failure.',
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: true,
localActiveChatgptAccountPresent: true,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex.cmd',
codexHome: '/Users/tester/.codex-custom',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
await expect(service.getConfiguredConnectionIssue({}, 'codex')).resolves.toBeNull();
expect(execCliMock).toHaveBeenCalledWith(
'/opt/codex/bin/codex.cmd',
['-c', 'forced_login_method="chatgpt"', 'login', 'status'],
expect.objectContaining({
timeout: 5_000,
windowsHide: true,
maxBuffer: 128 * 1024,
env: expect.objectContaining({
CODEX_CLI_PATH: '/opt/codex/bin/codex.cmd',
CODEX_HOME: '/Users/tester/.codex-custom',
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
}),
})
);
});
it('blocks launch when managed ChatGPT is selected but degraded exact runtime login is logged out', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const loginStatusChecker = vi.fn().mockResolvedValue({
status: 'not_logged_in',
detail: 'Not logged in',
});
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never,
loginStatusChecker
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'warning_degraded_but_launchable',
appServerState: 'degraded',
appServerStatusMessage: 'Using cached ChatGPT account after transient app-server failure.',
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: true,
localAccountArtifactsPresent: true,
localActiveChatgptAccountPresent: true,
runtimeContext: {
binaryPath: '/opt/codex/bin/codex',
codexHome: '/Users/tester/.codex-custom',
},
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const issue = await service.getConfiguredConnectionIssue(
{
OPENAI_API_KEY: 'ambient-openai-key',
CODEX_API_KEY: 'ambient-codex-key',
},
'codex'
);
expect(issue).toContain('Codex CLI login status is not active');
expect(issue).toContain('Reconnect ChatGPT');
expect(loginStatusChecker).toHaveBeenCalledWith({
binaryPath: '/opt/codex/bin/codex',
env: expect.objectContaining({
CODEX_CLI_PATH: '/opt/codex/bin/codex',
CODEX_HOME: '/Users/tester/.codex-custom',
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
}),
});
expect(loginStatusChecker.mock.calls[0]?.[0].env.OPENAI_API_KEY).toBeUndefined();
expect(loginStatusChecker.mock.calls[0]?.[0].env.CODEX_API_KEY).toBeUndefined();
});
it('reports a pinned Codex API-key mode as missing only the API key credential', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'api_key',
effectiveAuthMode: null,
launchAllowed: false,
launchIssueMessage: 'Add OPENAI_API_KEY or CODEX_API_KEY to use Codex API key mode.',
launchReadinessState: 'missing_auth',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: null,
apiKey: {
available: false,
source: null,
sourceLabel: null,
},
requiresOpenaiAuth: true,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const issue = await service.getConfiguredConnectionIssue({}, 'codex');
expect(issue).toBe(
'Codex API key mode is selected, but no OPENAI_API_KEY or CODEX_API_KEY credential is available. Add one before launching Codex.'
);
});
it('augments PTY env for native Codex without dropping existing OpenAI credentials', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const result = await service.augmentConfiguredConnectionEnv(
{
OPENAI_API_KEY: 'shell-key',
},
'codex'
);
expect(result.OPENAI_API_KEY).toBe('shell-key');
expect(result.CODEX_API_KEY).toBe('shell-key');
});
it('returns a chatgpt forced_login_method override for managed Codex launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexAccountFeature({
getSnapshot: vi.fn().mockResolvedValue({
preferredAuthMode: 'chatgpt',
effectiveAuthMode: 'chatgpt',
launchAllowed: true,
launchIssueMessage: null,
launchReadinessState: 'ready_chatgpt',
appServerState: 'healthy',
appServerStatusMessage: null,
managedAccount: {
type: 'chatgpt',
email: 'user@example.com',
planType: 'pro',
},
apiKey: {
available: true,
source: 'environment',
sourceLabel: 'Detected from OPENAI_API_KEY',
},
requiresOpenaiAuth: true,
login: {
status: 'idle',
error: null,
startedAt: null,
},
rateLimits: null,
updatedAt: '2026-04-20T00:00:00.000Z',
}),
} as never);
const args = await service.getConfiguredConnectionLaunchArgs(
{
OPENAI_API_KEY: undefined,
CODEX_API_KEY: undefined,
},
'codex',
undefined,
'/mock/claude-multimodel'
);
expect(args).toEqual(['--settings', '{"codex":{"forced_login_method":"chatgpt"}}']);
});
it('returns an api forced_login_method override for Codex API-key launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'OPENAI_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const args = await service.getConfiguredConnectionLaunchArgs(
{
OPENAI_API_KEY: 'stored-key',
CODEX_API_KEY: 'stored-key',
},
'codex',
undefined,
'/mock/claude-multimodel'
);
expect(args).toEqual(['--settings', '{"codex":{"forced_login_method":"api"}}']);
});
it('keeps codex exec style config overrides for direct Codex binary launches', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'OPENAI_API_KEY',
value: 'stored-key',
}),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
const args = await service.getConfiguredConnectionLaunchArgs(
{
OPENAI_API_KEY: 'stored-key',
CODEX_API_KEY: 'stored-key',
},
'codex',
undefined,
'/usr/local/bin/codex'
);
expect(args).toEqual(['-c', 'forced_login_method="api"']);
});
it('prefers the orchestrator Codex model catalog over the legacy direct app-server fallback', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const directCatalog = vi.fn().mockResolvedValue({
schemaVersion: 1,
providerId: 'codex',
source: 'app-server',
status: 'ready',
fetchedAt: '2026-04-28T00:00:00.000Z',
staleAt: '2026-04-28T00:10:00.000Z',
defaultModelId: 'gpt-5.4-mini',
defaultLaunchModel: 'gpt-5.4-mini',
models: [],
diagnostics: {
configReadState: 'ready',
appServerState: 'healthy',
},
});
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexModelCatalogFeature({ getCatalog: directCatalog } as never);
const enriched = await service.enrichProviderStatus({
providerId: 'codex',
displayName: 'Codex',
supported: true,
authenticated: true,
authMethod: 'chatgpt',
verificationState: 'verified',
models: ['gpt-5.4'],
modelCatalog: {
schemaVersion: 1,
providerId: 'codex',
source: 'app-server',
status: 'ready',
fetchedAt: '2026-04-28T00:00:00.000Z',
staleAt: '2026-04-28T00:10:00.000Z',
defaultModelId: 'gpt-5.4',
defaultLaunchModel: 'gpt-5.4',
models: [
{
id: 'gpt-5.4',
launchModel: 'gpt-5.4',
displayName: 'GPT-5.4',
hidden: false,
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
defaultReasoningEffort: 'medium',
inputModalities: ['text', 'image'],
supportsPersonality: false,
isDefault: true,
upgrade: false,
source: 'app-server',
},
{
id: 'gpt-5.5',
launchModel: 'gpt-5.5',
displayName: 'GPT-5.5',
hidden: false,
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
defaultReasoningEffort: 'high',
inputModalities: ['text', 'image'],
supportsPersonality: false,
isDefault: false,
upgrade: false,
source: 'app-server',
},
],
diagnostics: {
configReadState: 'skipped',
appServerState: 'healthy',
},
},
runtimeCapabilities: {
modelCatalog: { dynamic: true, source: 'app-server' },
},
canLoginFromUi: false,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: {
plugins: { status: 'unsupported', ownership: 'shared' },
mcp: { status: 'supported', ownership: 'shared' },
skills: { status: 'supported', ownership: 'shared' },
apiKeys: { status: 'supported', ownership: 'shared' },
},
},
});
expect(directCatalog).not.toHaveBeenCalled();
expect(enriched.models).toEqual(['gpt-5.4', 'gpt-5.5']);
expect(enriched.modelCatalog?.defaultLaunchModel).toBe('gpt-5.4');
expect(enriched.runtimeCapabilities?.modelCatalog).toEqual({
dynamic: true,
source: 'app-server',
});
});
it('skips Codex catalog hydration when summary enrichment disables catalog loading', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const directCatalog = vi.fn().mockResolvedValue({
schemaVersion: 1,
providerId: 'codex',
source: 'app-server',
status: 'ready',
fetchedAt: '2026-04-28T00:00:00.000Z',
staleAt: '2026-04-28T00:10:00.000Z',
defaultModelId: 'gpt-5.4',
defaultLaunchModel: 'gpt-5.4',
models: [],
diagnostics: {
configReadState: 'ready',
appServerState: 'healthy',
},
});
const service = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('auto'),
} as never
);
service.setCodexModelCatalogFeature({ getCatalog: directCatalog } as never);
const enriched = await service.enrichProviderStatus(
{
providerId: 'codex',
displayName: 'Codex',
supported: true,
authenticated: true,
authMethod: 'chatgpt',
verificationState: 'verified',
models: ['gpt-5.4'],
modelCatalog: null,
runtimeCapabilities: {
modelCatalog: { dynamic: true, source: 'app-server' },
},
canLoginFromUi: false,
capabilities: {
teamLaunch: true,
oneShot: true,
extensions: {
plugins: { status: 'unsupported', ownership: 'shared' },
mcp: { status: 'supported', ownership: 'shared' },
skills: { status: 'supported', ownership: 'shared' },
apiKeys: { status: 'supported', ownership: 'shared' },
},
},
},
{ hydrateModelCatalog: false }
);
expect(directCatalog).not.toHaveBeenCalled();
expect(enriched.models).toEqual(['gpt-5.4']);
expect(enriched.modelCatalog).toBeNull();
expect(enriched.runtimeCapabilities?.modelCatalog).toEqual({
dynamic: true,
source: 'app-server',
});
});
it('returns the stored Anthropic API key for team helper mode only in api_key auth mode', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-team-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
await expect(
service.getConfiguredAnthropicApiKeyForTeamRuntime({
ANTHROPIC_API_KEY: 'env-team-key',
})
).resolves.toBe('stored-team-key');
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY');
});
it('does not use stored Anthropic API keys for team helper mode with a compatible base URL', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-real-anthropic-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () =>
createConfig('api_key', {
enabled: true,
baseUrl: 'http://localhost:1234',
}),
} as never
);
await expect(
service.getConfiguredAnthropicApiKeyForTeamRuntime({
ANTHROPIC_BASE_URL: 'http://localhost:1234',
ANTHROPIC_API_KEY: '',
})
).resolves.toBeNull();
expect(lookupPreferred).not.toHaveBeenCalled();
});
it('ignores malformed Anthropic-compatible shell base URLs for team helper mode', async () => {
const lookupPreferred = vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-team-key',
});
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const service = new ProviderConnectionService(
{
lookupPreferred,
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
await expect(
service.getConfiguredAnthropicApiKeyForTeamRuntime({
ANTHROPIC_BASE_URL: 'not a url',
})
).resolves.toBe('stored-team-key');
expect(lookupPreferred).toHaveBeenCalledWith('ANTHROPIC_API_KEY');
});
it('does not use token-only or OAuth credentials for Anthropic team helper mode', async () => {
const { ProviderConnectionService } =
await import('@main/services/runtime/ProviderConnectionService');
const oauthService = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue({
envVarName: 'ANTHROPIC_API_KEY',
value: 'stored-team-key',
}),
} as never,
{
getConfig: () => createConfig('oauth'),
} as never
);
const apiKeyService = new ProviderConnectionService(
{
lookupPreferred: vi.fn().mockResolvedValue(null),
} as never,
{
getConfig: () => createConfig('api_key'),
} as never
);
await expect(
oauthService.getConfiguredAnthropicApiKeyForTeamRuntime({
ANTHROPIC_API_KEY: 'env-team-key',
})
).resolves.toBeNull();
await expect(
apiKeyService.getConfiguredAnthropicApiKeyForTeamRuntime({
ANTHROPIC_AUTH_TOKEN: 'proxy-token-only',
})
).resolves.toBeNull();
});
});