revert: revert KiloCode provider support
This reverts commit cc10485f0c.
This commit is contained in:
parent
cc10485f0c
commit
8e0731f47a
37 changed files with 55 additions and 964 deletions
|
|
@ -1,11 +0,0 @@
|
|||
import type {
|
||||
CliProviderModelCatalog,
|
||||
CliProviderModelCatalogItem,
|
||||
CliProviderModelCatalogSource,
|
||||
CliProviderModelCatalogStatus,
|
||||
} from '@shared/types';
|
||||
|
||||
export type KilocodeModelCatalogDto = CliProviderModelCatalog;
|
||||
export type KilocodeModelCatalogItemDto = CliProviderModelCatalogItem;
|
||||
export type KilocodeModelCatalogSourceDto = CliProviderModelCatalogSource;
|
||||
export type KilocodeModelCatalogStatusDto = CliProviderModelCatalogStatus;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export type {
|
||||
KilocodeModelCatalogDto,
|
||||
KilocodeModelCatalogItemDto,
|
||||
KilocodeModelCatalogSourceDto,
|
||||
KilocodeModelCatalogStatusDto,
|
||||
} from './dto';
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import type { KilocodeModelCatalogItemDto } from '../../contracts';
|
||||
|
||||
export function createStaticKilocodeModelCatalogModels(): KilocodeModelCatalogItemDto[] {
|
||||
return [
|
||||
{
|
||||
id: 'claude-sonnet-4-5',
|
||||
launchModel: 'claude-sonnet-4-5',
|
||||
displayName: 'Claude Sonnet 4.5',
|
||||
hidden: false,
|
||||
supportedReasoningEfforts: [],
|
||||
defaultReasoningEffort: null,
|
||||
inputModalities: ['text'],
|
||||
supportsPersonality: false,
|
||||
isDefault: true,
|
||||
upgrade: false,
|
||||
source: 'static-fallback',
|
||||
},
|
||||
{
|
||||
id: 'claude-opus-4-5',
|
||||
launchModel: 'claude-opus-4-5',
|
||||
displayName: 'Claude Opus 4.5',
|
||||
hidden: false,
|
||||
supportedReasoningEfforts: [],
|
||||
defaultReasoningEffort: null,
|
||||
inputModalities: ['text'],
|
||||
supportsPersonality: false,
|
||||
isDefault: false,
|
||||
upgrade: false,
|
||||
source: 'static-fallback',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
export type {
|
||||
KilocodeModelCatalogDto,
|
||||
KilocodeModelCatalogItemDto,
|
||||
KilocodeModelCatalogSourceDto,
|
||||
KilocodeModelCatalogStatusDto,
|
||||
} from './contracts';
|
||||
export type { KilocodeModelCatalogFeatureFacade, KilocodeModelCatalogRequest } from './main';
|
||||
export { createKilocodeModelCatalogFeature } from './main';
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import { createHash } from 'node:crypto';
|
||||
|
||||
import { createStaticKilocodeModelCatalogModels } from '../../core/domain/kilocodeModelCatalogFallback';
|
||||
import { InMemoryKilocodeModelCatalogCache } from '../infrastructure/InMemoryKilocodeModelCatalogCache';
|
||||
import { KilocodeGatewayClient } from '../infrastructure/KilocodeGatewayClient';
|
||||
|
||||
import type { KilocodeModelCatalogDto, KilocodeModelCatalogItemDto } from '../../contracts';
|
||||
import type { Logger } from '@shared/utils/logger';
|
||||
|
||||
type LoggerPort = Pick<Logger, 'warn'>;
|
||||
|
||||
const CATALOG_CACHE_TTL_MS = 10 * 60_000;
|
||||
const CATALOG_STALE_TTL_MS = 24 * 60 * 60_000;
|
||||
|
||||
export interface KilocodeModelCatalogRequest {
|
||||
apiKey?: string | null;
|
||||
forceRefresh?: boolean;
|
||||
}
|
||||
|
||||
export interface KilocodeModelCatalogFeatureFacade {
|
||||
getCatalog(options?: KilocodeModelCatalogRequest): Promise<KilocodeModelCatalogDto>;
|
||||
invalidate(): void;
|
||||
}
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function staleAtIso(): string {
|
||||
return new Date(Date.now() + CATALOG_CACHE_TTL_MS).toISOString();
|
||||
}
|
||||
|
||||
function buildCacheKey(apiKey: string): string {
|
||||
return `kilocode:${createHash('sha256').update(apiKey).digest('hex')}`;
|
||||
}
|
||||
|
||||
function normalizeGatewayModels(
|
||||
models: { id: string; displayName: string }[]
|
||||
): KilocodeModelCatalogItemDto[] {
|
||||
return models.map((model, index) => ({
|
||||
id: model.id,
|
||||
launchModel: model.id,
|
||||
displayName: model.displayName,
|
||||
hidden: false,
|
||||
supportedReasoningEfforts: [],
|
||||
defaultReasoningEffort: null,
|
||||
inputModalities: ['text'],
|
||||
supportsPersonality: false,
|
||||
isDefault: index === 0,
|
||||
upgrade: false,
|
||||
source: 'app-server' as const,
|
||||
}));
|
||||
}
|
||||
|
||||
function createFallbackCatalog(options: {
|
||||
message: string;
|
||||
status?: KilocodeModelCatalogDto['status'];
|
||||
appServerState: KilocodeModelCatalogDto['diagnostics']['appServerState'];
|
||||
}): KilocodeModelCatalogDto {
|
||||
const models = createStaticKilocodeModelCatalogModels();
|
||||
const defaultModel = models.find((m) => m.isDefault) ?? models[0] ?? null;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
providerId: 'kilocode',
|
||||
source: 'static-fallback',
|
||||
status: options.status ?? 'degraded',
|
||||
fetchedAt: nowIso(),
|
||||
staleAt: staleAtIso(),
|
||||
defaultModelId: defaultModel?.id ?? null,
|
||||
defaultLaunchModel: defaultModel?.launchModel ?? null,
|
||||
models,
|
||||
diagnostics: {
|
||||
configReadState: 'skipped',
|
||||
appServerState: options.appServerState,
|
||||
message: options.message,
|
||||
code: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createKilocodeModelCatalogFeature(options: {
|
||||
logger: LoggerPort;
|
||||
}): KilocodeModelCatalogFeatureFacade {
|
||||
const cache = new InMemoryKilocodeModelCatalogCache();
|
||||
const inFlightRefreshes = new Map<string, Promise<KilocodeModelCatalogDto>>();
|
||||
const client = new KilocodeGatewayClient();
|
||||
|
||||
async function getCatalog(
|
||||
request: KilocodeModelCatalogRequest = {}
|
||||
): Promise<KilocodeModelCatalogDto> {
|
||||
const apiKey = request.apiKey?.trim() || process.env.KILO_API_KEY?.trim() || null;
|
||||
|
||||
if (!apiKey) {
|
||||
return createFallbackCatalog({
|
||||
message: 'No KiloCode API key configured. Set KILO_API_KEY or configure an API key.',
|
||||
appServerState: 'runtime-missing',
|
||||
status: 'unavailable',
|
||||
});
|
||||
}
|
||||
|
||||
const cacheKey = buildCacheKey(apiKey);
|
||||
|
||||
if (request.forceRefresh !== true) {
|
||||
const cached = cache.get(cacheKey, CATALOG_CACHE_TTL_MS);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
const existing = inFlightRefreshes.get(cacheKey);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const refreshPromise = (async (): Promise<KilocodeModelCatalogDto> => {
|
||||
try {
|
||||
const gatewayModels = await client.listModels(apiKey);
|
||||
const models = normalizeGatewayModels(gatewayModels);
|
||||
|
||||
if (models.length === 0) {
|
||||
throw new Error('KiloCode gateway returned no models.');
|
||||
}
|
||||
|
||||
const defaultModel = models[0] ?? null;
|
||||
const catalog: KilocodeModelCatalogDto = {
|
||||
schemaVersion: 1,
|
||||
providerId: 'kilocode',
|
||||
source: 'app-server',
|
||||
status: 'ready',
|
||||
fetchedAt: nowIso(),
|
||||
staleAt: staleAtIso(),
|
||||
defaultModelId: defaultModel?.id ?? null,
|
||||
defaultLaunchModel: defaultModel?.launchModel ?? null,
|
||||
models,
|
||||
diagnostics: {
|
||||
configReadState: 'skipped',
|
||||
appServerState: 'healthy',
|
||||
message: null,
|
||||
code: null,
|
||||
},
|
||||
};
|
||||
|
||||
cache.set(cacheKey, catalog);
|
||||
return catalog;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const stale = cache.getLatest(cacheKey);
|
||||
if (stale && Date.parse(stale.fetchedAt) + CATALOG_STALE_TTL_MS > Date.now()) {
|
||||
return {
|
||||
...stale,
|
||||
status: 'stale',
|
||||
diagnostics: {
|
||||
configReadState: 'skipped',
|
||||
appServerState: 'degraded',
|
||||
message,
|
||||
code: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
options.logger.warn('KiloCode model catalog refresh failed', { error: message });
|
||||
return createFallbackCatalog({
|
||||
message,
|
||||
appServerState: 'degraded',
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
inFlightRefreshes.set(cacheKey, refreshPromise);
|
||||
try {
|
||||
return await refreshPromise;
|
||||
} finally {
|
||||
if (inFlightRefreshes.get(cacheKey) === refreshPromise) {
|
||||
inFlightRefreshes.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getCatalog,
|
||||
invalidate: () => {
|
||||
cache.clear();
|
||||
inFlightRefreshes.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export type {
|
||||
KilocodeModelCatalogFeatureFacade,
|
||||
KilocodeModelCatalogRequest,
|
||||
} from './composition/createKilocodeModelCatalogFeature';
|
||||
export { createKilocodeModelCatalogFeature } from './composition/createKilocodeModelCatalogFeature';
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import type { KilocodeModelCatalogDto } from '../../contracts';
|
||||
|
||||
interface CacheEntry {
|
||||
value: KilocodeModelCatalogDto;
|
||||
observedAt: number;
|
||||
}
|
||||
|
||||
export class InMemoryKilocodeModelCatalogCache {
|
||||
private readonly entries = new Map<string, CacheEntry>();
|
||||
|
||||
get(key: string, maxAgeMs: number): KilocodeModelCatalogDto | null {
|
||||
const entry = this.entries.get(key);
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() - entry.observedAt > maxAgeMs) {
|
||||
return null;
|
||||
}
|
||||
return structuredClone(entry.value);
|
||||
}
|
||||
|
||||
getLatest(key: string): KilocodeModelCatalogDto | null {
|
||||
const entry = this.entries.get(key);
|
||||
return entry ? structuredClone(entry.value) : null;
|
||||
}
|
||||
|
||||
set(key: string, value: KilocodeModelCatalogDto): void {
|
||||
this.entries.set(key, {
|
||||
value: structuredClone(value),
|
||||
observedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.entries.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import https from 'node:https';
|
||||
|
||||
const GATEWAY_BASE_URL = 'https://api.kilo.ai';
|
||||
// KiloCode gateway endpoint: https://kilo.ai/docs/gateway/models-and-providers
|
||||
const MODELS_PATH = '/api/gateway/models';
|
||||
const REQUEST_TIMEOUT_MS = 8_000;
|
||||
const ERROR_BODY_PREVIEW_LIMIT = 500;
|
||||
|
||||
interface GatewayModelObject {
|
||||
id?: string;
|
||||
object?: string;
|
||||
created?: number;
|
||||
owned_by?: string;
|
||||
display_name?: string;
|
||||
}
|
||||
|
||||
interface GatewayModelsResponse {
|
||||
object?: string;
|
||||
data?: GatewayModelObject[];
|
||||
}
|
||||
|
||||
export interface KilocodeGatewayModel {
|
||||
id: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
function sanitizeErrorBody(body: string): string {
|
||||
const sanitized = body
|
||||
.trim()
|
||||
.replace(/Bearer\s+[A-Za-z0-9._~-]+/gi, 'Bearer [redacted]')
|
||||
.replace(/sk-[A-Za-z0-9_-]+/g, '[redacted-api-key]');
|
||||
if (!sanitized) {
|
||||
return 'empty response body';
|
||||
}
|
||||
return sanitized.length > ERROR_BODY_PREVIEW_LIMIT
|
||||
? `${sanitized.slice(0, ERROR_BODY_PREVIEW_LIMIT)}...`
|
||||
: sanitized;
|
||||
}
|
||||
|
||||
export class KilocodeGatewayClient {
|
||||
async listModels(apiKey: string): Promise<KilocodeGatewayModel[]> {
|
||||
const raw = await this.fetchModels(apiKey);
|
||||
const items = raw.data ?? [];
|
||||
return items
|
||||
.filter(
|
||||
(item): item is GatewayModelObject & { id: string } =>
|
||||
typeof item.id === 'string' && item.id.trim().length > 0
|
||||
)
|
||||
.map((item) => ({
|
||||
id: item.id.trim(),
|
||||
displayName: (item.display_name ?? item.id).trim(),
|
||||
}));
|
||||
}
|
||||
|
||||
private fetchModels(apiKey: string): Promise<GatewayModelsResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(MODELS_PATH, GATEWAY_BASE_URL);
|
||||
const options: https.RequestOptions = {
|
||||
hostname: url.hostname,
|
||||
port: url.port || 443,
|
||||
path: url.pathname + url.search,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
res.on('end', () => {
|
||||
const body = Buffer.concat(chunks).toString('utf8');
|
||||
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
|
||||
reject(
|
||||
new Error(
|
||||
`KiloCode gateway responded with HTTP ${res.statusCode}: ${sanitizeErrorBody(body)}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(body) as GatewayModelsResponse);
|
||||
} catch {
|
||||
reject(
|
||||
new Error(`KiloCode gateway returned non-JSON response: ${sanitizeErrorBody(body)}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
res.on('error', reject);
|
||||
});
|
||||
|
||||
req.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
||||
req.destroy(new Error(`KiloCode gateway request timed out after ${REQUEST_TIMEOUT_MS}ms`));
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ export type MemberWorkSyncActionableWorkPriority =
|
|||
| 'blocked'
|
||||
| 'needs_clarification';
|
||||
|
||||
export type MemberWorkSyncProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode' | 'kilocode';
|
||||
export type MemberWorkSyncProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode';
|
||||
|
||||
export type MemberWorkSyncReviewObligation = 'review_pickup_required' | 'review_in_progress';
|
||||
|
||||
|
|
|
|||
|
|
@ -384,7 +384,6 @@ const BRAND_ALIASES: Record<string, string> = {
|
|||
'gitlab-duo': 'gitlab-duo',
|
||||
'google-vertex': 'google-vertex',
|
||||
'hugging-face': 'huggingface',
|
||||
kilocode: 'kilo',
|
||||
'mistral-ai': 'mistral',
|
||||
'ollama-cloud': 'ollama-cloud',
|
||||
'opencode-zen': 'opencode',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
export type WorkspaceTrustProvider =
|
||||
| 'claude'
|
||||
| 'anthropic'
|
||||
| 'codex'
|
||||
| 'gemini'
|
||||
| 'opencode'
|
||||
| 'kilocode';
|
||||
export type WorkspaceTrustProvider = 'claude' | 'anthropic' | 'codex' | 'gemini' | 'opencode';
|
||||
|
||||
export type WorkspaceTrustWorkspaceSource =
|
||||
| 'team-root'
|
||||
|
|
|
|||
|
|
@ -32,10 +32,6 @@ import {
|
|||
type CodexModelCatalogFeatureFacade,
|
||||
createCodexModelCatalogFeature,
|
||||
} from '@features/codex-model-catalog/main';
|
||||
import {
|
||||
type KilocodeModelCatalogFeatureFacade,
|
||||
createKilocodeModelCatalogFeature,
|
||||
} from '@features/kilocode-model-catalog/main';
|
||||
import {
|
||||
createMemberLogStreamFeature,
|
||||
registerMemberLogStreamIpc,
|
||||
|
|
@ -903,7 +899,6 @@ let updaterService: UpdaterService;
|
|||
let sshConnectionManager: SshConnectionManager;
|
||||
let codexAccountFeature: CodexAccountFeatureFacade | null = null;
|
||||
let codexModelCatalogFeature: CodexModelCatalogFeatureFacade | null = null;
|
||||
let kilocodeModelCatalogFeature: KilocodeModelCatalogFeatureFacade | null = null;
|
||||
let recentProjectsFeature: RecentProjectsFeatureFacade;
|
||||
let runtimeProviderManagementFeature: RuntimeProviderManagementFeatureFacade;
|
||||
let memberWorkSyncFeature: MemberWorkSyncFeatureFacade | null = null;
|
||||
|
|
@ -2108,10 +2103,6 @@ async function initializeServices(): Promise<void> {
|
|||
codexAccountFeature,
|
||||
});
|
||||
providerConnectionService.setCodexModelCatalogFeature(codexModelCatalogFeature);
|
||||
kilocodeModelCatalogFeature = createKilocodeModelCatalogFeature({
|
||||
logger: createLogger('Feature:KilocodeModelCatalog'),
|
||||
});
|
||||
providerConnectionService.setKilocodeModelCatalogFeature(kilocodeModelCatalogFeature);
|
||||
|
||||
// startProcessHealthPolling() is deferred to after window creation
|
||||
// (did-finish-load handler) to avoid thread pool contention at startup.
|
||||
|
|
@ -2364,13 +2355,10 @@ async function shutdownServices(): Promise<void> {
|
|||
await runShutdownStep('skills watcher stop', () => skillsWatcherService?.stopAll());
|
||||
await runShutdownStep('provider connection feature detach', () => {
|
||||
providerConnectionService.setCodexModelCatalogFeature(null);
|
||||
providerConnectionService.setKilocodeModelCatalogFeature(null);
|
||||
providerConnectionService.setCodexAccountFeature(null);
|
||||
});
|
||||
await runShutdownStep('Codex model catalog dispose', () => codexModelCatalogFeature?.dispose());
|
||||
codexModelCatalogFeature = null;
|
||||
kilocodeModelCatalogFeature?.invalidate();
|
||||
kilocodeModelCatalogFeature = null;
|
||||
await runShutdownStep('Codex account dispose', () => codexAccountFeature?.dispose());
|
||||
codexAccountFeature = null;
|
||||
await runShutdownStep('member work sync dispose', () => memberWorkSyncFeature?.dispose());
|
||||
|
|
|
|||
|
|
@ -44,12 +44,7 @@ const cachedStatus = new Map<
|
|||
>();
|
||||
let statusCacheGeneration = 0;
|
||||
const STATUS_CACHE_TTL_MS = 5_000;
|
||||
const FRONTEND_MULTIMODEL_PROVIDER_IDS = new Set<CliProviderId>([
|
||||
'anthropic',
|
||||
'codex',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
]);
|
||||
const FRONTEND_MULTIMODEL_PROVIDER_IDS = new Set<CliProviderId>(['anthropic', 'codex', 'opencode']);
|
||||
|
||||
function isFrontendMultimodelProviderId(providerId: CliProviderId): boolean {
|
||||
return FRONTEND_MULTIMODEL_PROVIDER_IDS.has(providerId);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import {
|
|||
type ProviderModelAvailabilityContext,
|
||||
type ProviderModelAvailabilitySnapshot,
|
||||
} from '../runtime/CliProviderModelAvailabilityService';
|
||||
import { providerConnectionService } from '../runtime/ProviderConnectionService';
|
||||
import { ClaudeBinaryResolver } from '../team/ClaudeBinaryResolver';
|
||||
import { getCliFlavorUiOptions, getConfiguredCliFlavor } from '../team/cliFlavor';
|
||||
|
||||
|
|
@ -72,12 +71,7 @@ const GCS_BASE =
|
|||
'https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases';
|
||||
|
||||
const CLI_INSTALLER_PROGRESS_CHANNEL = 'cliInstaller:progress';
|
||||
const FRONTEND_MULTIMODEL_PROVIDER_IDS: CliProviderId[] = [
|
||||
'anthropic',
|
||||
'codex',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
];
|
||||
const FRONTEND_MULTIMODEL_PROVIDER_IDS: CliProviderId[] = ['anthropic', 'codex', 'opencode'];
|
||||
const FRONTEND_MULTIMODEL_PROVIDER_ID_SET = new Set<CliProviderId>(
|
||||
FRONTEND_MULTIMODEL_PROVIDER_IDS
|
||||
);
|
||||
|
|
@ -92,8 +86,6 @@ function getProviderDisplayName(providerId: CliProviderId): string {
|
|||
return 'Gemini';
|
||||
case 'opencode':
|
||||
return 'OpenCode (200+ models)';
|
||||
case 'kilocode':
|
||||
return 'KiloCode';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -927,10 +919,6 @@ export class CliInstallerService {
|
|||
background: false,
|
||||
});
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
return this.resolveKilocodeProviderStatus();
|
||||
}
|
||||
|
||||
const binaryPath = await ClaudeBinaryResolver.resolve();
|
||||
if (!binaryPath) {
|
||||
return null;
|
||||
|
|
@ -971,10 +959,6 @@ export class CliInstallerService {
|
|||
background: false,
|
||||
});
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
return this.resolveKilocodeProviderStatus();
|
||||
}
|
||||
|
||||
const binaryPath = await ClaudeBinaryResolver.resolve();
|
||||
if (!binaryPath) {
|
||||
return null;
|
||||
|
|
@ -1220,53 +1204,6 @@ export class CliInstallerService {
|
|||
result.authMethod = null;
|
||||
}
|
||||
|
||||
private async resolveKilocodeProviderStatus(): Promise<CliProviderStatus> {
|
||||
const baseStatus: CliProviderStatus = {
|
||||
providerId: 'kilocode',
|
||||
displayName: 'KiloCode',
|
||||
supported: false,
|
||||
authenticated: false,
|
||||
authMethod: null,
|
||||
verificationState: 'verified',
|
||||
modelVerificationState: 'idle',
|
||||
statusMessage: null,
|
||||
detailMessage: null,
|
||||
models: [],
|
||||
modelAvailability: [],
|
||||
canLoginFromUi: true,
|
||||
capabilities: {
|
||||
teamLaunch: false,
|
||||
oneShot: false,
|
||||
extensions: createDefaultCliExtensionCapabilities(),
|
||||
},
|
||||
selectedBackendId: null,
|
||||
resolvedBackendId: null,
|
||||
availableBackends: [],
|
||||
externalRuntimeDiagnostics: [],
|
||||
backend: null,
|
||||
connection: null,
|
||||
modelCatalog: null,
|
||||
runtimeCapabilities: null,
|
||||
subscriptionRateLimits: null,
|
||||
};
|
||||
// enrichProviderStatus checks both the app key store and process.env for KILO_API_KEY
|
||||
const enriched = await providerConnectionService.enrichProviderStatus(baseStatus);
|
||||
const hasApiKey = Boolean(enriched.connection?.apiKeyConfigured);
|
||||
const status: CliProviderStatus = {
|
||||
...enriched,
|
||||
supported: hasApiKey,
|
||||
authenticated: hasApiKey,
|
||||
authMethod: hasApiKey ? 'api_key' : null,
|
||||
statusMessage: hasApiKey ? null : 'Configure KILO_API_KEY to use KiloCode.',
|
||||
capabilities: {
|
||||
...enriched.capabilities,
|
||||
teamLaunch: hasApiKey,
|
||||
},
|
||||
};
|
||||
this.updateLatestProviderStatus(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
private markProvidersDeferred(result: CliInstallationStatus): void {
|
||||
if (result.flavor !== 'agent_teams_orchestrator') {
|
||||
return;
|
||||
|
|
@ -1304,38 +1241,13 @@ export class CliInstallerService {
|
|||
if (result.flavor === 'agent_teams_orchestrator') {
|
||||
result.authStatusChecking = true;
|
||||
let statusTarget = result;
|
||||
const buildFrontendProviders = async (
|
||||
providersSnapshot: CliProviderStatus[]
|
||||
): Promise<CliProviderStatus[]> => {
|
||||
const providersById = new Map(providersSnapshot.map((provider) => [provider.providerId, provider]));
|
||||
const frontendProviders: CliProviderStatus[] = [];
|
||||
for (const providerId of FRONTEND_MULTIMODEL_PROVIDER_IDS) {
|
||||
if (providerId === 'kilocode') {
|
||||
frontendProviders.push(await this.resolveKilocodeProviderStatus());
|
||||
continue;
|
||||
}
|
||||
const provider = providersById.get(providerId);
|
||||
if (provider) {
|
||||
frontendProviders.push(provider);
|
||||
}
|
||||
}
|
||||
return frontendProviders;
|
||||
};
|
||||
const applyProviders = async (
|
||||
providersSnapshot: CliProviderStatus[],
|
||||
final: boolean
|
||||
): Promise<void> => {
|
||||
const applyProviders = (providersSnapshot: CliProviderStatus[], final: boolean): void => {
|
||||
if (generation !== this.statusGatherGeneration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = statusTarget;
|
||||
const frontendProviders = await buildFrontendProviders(
|
||||
filterFrontendMultimodelProviders(providersSnapshot)
|
||||
);
|
||||
if (generation !== this.statusGatherGeneration) {
|
||||
return;
|
||||
}
|
||||
const frontendProviders = filterFrontendMultimodelProviders(providersSnapshot);
|
||||
target.providers = frontendProviders;
|
||||
target.authLoggedIn = hasFrontendAuthenticatedProvider(frontendProviders);
|
||||
target.authMethod = getFrontendAuthenticatedProvider(frontendProviders)?.authMethod ?? null;
|
||||
|
|
@ -1348,10 +1260,10 @@ export class CliInstallerService {
|
|||
|
||||
const completion = this.multimodelBridgeService
|
||||
.getProviderStatuses(binaryPath, (providersSnapshot) => {
|
||||
void applyProviders(providersSnapshot, false);
|
||||
applyProviders(providersSnapshot, false);
|
||||
})
|
||||
.then((providers) => {
|
||||
return applyProviders(providers, true);
|
||||
applyProviders(providers, true);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (generation !== this.statusGatherGeneration) {
|
||||
|
|
|
|||
|
|
@ -335,8 +335,6 @@ function getProviderDisplayName(providerId: CliProviderId): string {
|
|||
return 'Gemini';
|
||||
case 'opencode':
|
||||
return 'OpenCode (200+ models)';
|
||||
case 'kilocode':
|
||||
return 'KiloCode';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,14 +421,15 @@ function mapRuntimeExtensionCapabilities(
|
|||
const defaults = capabilities
|
||||
? createDefaultCliExtensionCapabilities()
|
||||
: createLegacyRuntimeFallbackCliExtensionCapabilities();
|
||||
const isExternalRuntime = providerId === 'opencode' || providerId === 'kilocode';
|
||||
const pluginStatus = isExternalRuntime
|
||||
? 'unsupported'
|
||||
: (capabilities?.plugins?.status ?? defaults.plugins.status);
|
||||
const pluginReason = isExternalRuntime
|
||||
? (capabilities?.plugins?.reason ??
|
||||
`${getProviderDisplayName(providerId)} does not support plugin management from Agent Teams.`)
|
||||
: (capabilities?.plugins?.reason ?? defaults.plugins.reason);
|
||||
const pluginStatus =
|
||||
providerId === 'opencode'
|
||||
? 'unsupported'
|
||||
: (capabilities?.plugins?.status ?? defaults.plugins.status);
|
||||
const pluginReason =
|
||||
providerId === 'opencode'
|
||||
? (capabilities?.plugins?.reason ??
|
||||
'OpenCode does not support plugin management from Agent Teams.')
|
||||
: (capabilities?.plugins?.reason ?? defaults.plugins.reason);
|
||||
|
||||
return {
|
||||
plugins: {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import type {
|
|||
CodexModelCatalogFeatureFacade,
|
||||
CodexModelCatalogRequest,
|
||||
} from '@features/codex-model-catalog/main';
|
||||
import type { KilocodeModelCatalogFeatureFacade } from '@features/kilocode-model-catalog/main';
|
||||
import type {
|
||||
CliProviderAuthMode,
|
||||
CliProviderConnectionInfo,
|
||||
|
|
@ -66,18 +65,12 @@ const PROVIDER_CAPABILITIES: Record<
|
|||
supportsApiKey: false,
|
||||
configurableAuthModes: [],
|
||||
},
|
||||
kilocode: {
|
||||
supportsOAuth: false,
|
||||
supportsApiKey: true,
|
||||
configurableAuthModes: ['api_key'],
|
||||
},
|
||||
};
|
||||
|
||||
const PROVIDER_API_KEY_ENV_VARS: Partial<Record<CliProviderId, string>> = {
|
||||
anthropic: 'ANTHROPIC_API_KEY',
|
||||
codex: 'OPENAI_API_KEY',
|
||||
gemini: 'GEMINI_API_KEY',
|
||||
kilocode: 'KILO_API_KEY',
|
||||
};
|
||||
|
||||
const ANTHROPIC_BASE_URL_ENV_VAR = 'ANTHROPIC_BASE_URL';
|
||||
|
|
@ -371,10 +364,6 @@ export class ProviderConnectionService {
|
|||
private codexAccountFeature: CodexAccountSnapshotReader | null = null;
|
||||
private codexModelCatalogFeature: Pick<CodexModelCatalogFeatureFacade, 'getCatalog'> | null =
|
||||
null;
|
||||
private kilocodeModelCatalogFeature: Pick<
|
||||
KilocodeModelCatalogFeatureFacade,
|
||||
'getCatalog'
|
||||
> | null = null;
|
||||
private readonly anthropicApiKeyVerificationCache = new Map<
|
||||
string,
|
||||
{ result: AnthropicApiKeyVerificationResult; at: number }
|
||||
|
|
@ -402,12 +391,6 @@ export class ProviderConnectionService {
|
|||
this.codexModelCatalogFeature = feature;
|
||||
}
|
||||
|
||||
setKilocodeModelCatalogFeature(
|
||||
feature: Pick<KilocodeModelCatalogFeatureFacade, 'getCatalog'> | null
|
||||
): void {
|
||||
this.kilocodeModelCatalogFeature = feature;
|
||||
}
|
||||
|
||||
async getCodexModelCatalog(
|
||||
request: CodexModelCatalogRequest = {}
|
||||
): Promise<CodexModelCatalogDto | null> {
|
||||
|
|
@ -435,10 +418,6 @@ export class ProviderConnectionService {
|
|||
return this.configManager.getConfig().providerConnections.codex.preferredAuthMode;
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
return 'api_key';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -620,16 +599,6 @@ export class ProviderConnectionService {
|
|||
return env;
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
const apiKey = await this.resolveProviderApiKeyForEnv(env, 'kilocode', options);
|
||||
if (apiKey) {
|
||||
env.KILO_API_KEY = apiKey;
|
||||
} else if (typeof env.KILO_API_KEY === 'string' && !env.KILO_API_KEY.trim()) {
|
||||
delete env.KILO_API_KEY;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
if (providerId !== 'codex') {
|
||||
return env;
|
||||
}
|
||||
|
|
@ -676,7 +645,7 @@ export class ProviderConnectionService {
|
|||
options?: StoredApiKeyAccessOptions
|
||||
): Promise<NodeJS.ProcessEnv> {
|
||||
let nextEnv = env;
|
||||
for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode', 'kilocode'] as const) {
|
||||
for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode'] as const) {
|
||||
nextEnv = await this.applyConfiguredConnectionEnv(nextEnv, providerId, undefined, options);
|
||||
}
|
||||
return nextEnv;
|
||||
|
|
@ -712,14 +681,6 @@ export class ProviderConnectionService {
|
|||
return env;
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
const apiKey = await this.resolveProviderApiKeyForEnv(env, 'kilocode', options);
|
||||
if (apiKey) {
|
||||
env.KILO_API_KEY = apiKey;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
if (providerId !== 'codex') {
|
||||
return env;
|
||||
}
|
||||
|
|
@ -761,7 +722,7 @@ export class ProviderConnectionService {
|
|||
options?: StoredApiKeyAccessOptions
|
||||
): Promise<NodeJS.ProcessEnv> {
|
||||
let nextEnv = env;
|
||||
for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode', 'kilocode'] as const) {
|
||||
for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode'] as const) {
|
||||
nextEnv = await this.augmentConfiguredConnectionEnv(nextEnv, providerId, undefined, options);
|
||||
}
|
||||
return nextEnv;
|
||||
|
|
@ -804,22 +765,6 @@ export class ProviderConnectionService {
|
|||
);
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
if (typeof env.KILO_API_KEY === 'string' && env.KILO_API_KEY.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (await this.hasStoredApiKey('KILO_API_KEY')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.getExternalCredential('kilocode')?.value.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'KiloCode API key is not configured. Set KILO_API_KEY or add it in Provider Settings.';
|
||||
}
|
||||
|
||||
if (providerId !== 'codex') {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -902,13 +847,7 @@ export class ProviderConnectionService {
|
|||
|
||||
async getConfiguredConnectionIssues(
|
||||
env: NodeJS.ProcessEnv,
|
||||
providerIds: readonly CliProviderId[] = [
|
||||
'anthropic',
|
||||
'codex',
|
||||
'gemini',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
],
|
||||
providerIds: readonly CliProviderId[] = ['anthropic', 'codex', 'gemini', 'opencode'],
|
||||
runtimeBackendOverrides?: Partial<Record<CliProviderId, string>>
|
||||
): Promise<Partial<Record<CliProviderId, string>>> {
|
||||
const issues: Partial<Record<CliProviderId, string>> = {};
|
||||
|
|
@ -977,10 +916,6 @@ export class ProviderConnectionService {
|
|||
return this.enrichAnthropicProviderStatus(withConnection);
|
||||
}
|
||||
|
||||
if (provider.providerId === 'kilocode') {
|
||||
return this.enrichKilocodeProviderStatus(withConnection);
|
||||
}
|
||||
|
||||
if (provider.providerId !== 'codex') {
|
||||
return withConnection;
|
||||
}
|
||||
|
|
@ -1045,33 +980,6 @@ export class ProviderConnectionService {
|
|||
}
|
||||
}
|
||||
|
||||
private async enrichKilocodeProviderStatus(
|
||||
provider: CliProviderStatus
|
||||
): Promise<CliProviderStatus> {
|
||||
if (!this.kilocodeModelCatalogFeature || !provider.connection?.apiKeyConfigured) {
|
||||
return provider;
|
||||
}
|
||||
try {
|
||||
const catalog = await this.kilocodeModelCatalogFeature.getCatalog({
|
||||
apiKey: await this.resolveStoredOrExternalProviderApiKey('kilocode'),
|
||||
});
|
||||
if (catalog.status === 'unavailable' || catalog.models.length === 0) {
|
||||
return provider;
|
||||
}
|
||||
const models = catalog.models
|
||||
.filter((m) => !m.hidden)
|
||||
.map((m) => m.launchModel.trim())
|
||||
.filter(Boolean);
|
||||
return {
|
||||
...provider,
|
||||
models: models.length > 0 ? models : provider.models,
|
||||
modelCatalog: catalog,
|
||||
};
|
||||
} catch {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
private async enrichAnthropicProviderStatus(
|
||||
provider: CliProviderStatus
|
||||
): Promise<CliProviderStatus> {
|
||||
|
|
@ -1288,46 +1196,6 @@ export class ProviderConnectionService {
|
|||
return this.apiKeyService.lookupPreferred(envVarName);
|
||||
}
|
||||
|
||||
private async resolveStoredOrExternalProviderApiKey(
|
||||
providerId: CliProviderId,
|
||||
options?: StoredApiKeyAccessOptions
|
||||
): Promise<string | null> {
|
||||
const envVarName = PROVIDER_API_KEY_ENV_VARS[providerId];
|
||||
if (!envVarName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const storedKey = await this.lookupStoredApiKeyValue(envVarName, options);
|
||||
if (storedKey?.value.trim()) {
|
||||
return storedKey.value.trim();
|
||||
}
|
||||
|
||||
return this.getExternalCredential(providerId)?.value.trim() || null;
|
||||
}
|
||||
|
||||
private async resolveProviderApiKeyForEnv(
|
||||
env: NodeJS.ProcessEnv,
|
||||
providerId: CliProviderId,
|
||||
options?: StoredApiKeyAccessOptions
|
||||
): Promise<string | null> {
|
||||
const envVarName = PROVIDER_API_KEY_ENV_VARS[providerId];
|
||||
if (!envVarName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const storedKey = await this.lookupStoredApiKeyValue(envVarName, options);
|
||||
if (storedKey?.value.trim()) {
|
||||
return storedKey.value.trim();
|
||||
}
|
||||
|
||||
const existingValue = env[envVarName];
|
||||
if (typeof existingValue === 'string' && existingValue.trim()) {
|
||||
return existingValue.trim();
|
||||
}
|
||||
|
||||
return this.getExternalCredential(providerId)?.value.trim() || null;
|
||||
}
|
||||
|
||||
private getConfiguredCodexRuntimeBackend(runtimeBackendOverride?: string | null): 'codex-native' {
|
||||
if (runtimeBackendOverride === CODEX_NATIVE_BACKEND_ID) {
|
||||
return runtimeBackendOverride;
|
||||
|
|
@ -1509,16 +1377,6 @@ export class ProviderConnectionService {
|
|||
}
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
const apiKey = this.getExternalEnvValue('KILO_API_KEY');
|
||||
if (apiKey) {
|
||||
return {
|
||||
label: 'Detected from KILO_API_KEY',
|
||||
value: apiKey,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,12 +103,7 @@ export function applyProviderRuntimeEnv(
|
|||
export function resolveRuntimeProviderId(
|
||||
providerId: RuntimeEnvProviderId | undefined
|
||||
): CliProviderId {
|
||||
if (
|
||||
providerId === 'codex' ||
|
||||
providerId === 'gemini' ||
|
||||
providerId === 'opencode' ||
|
||||
providerId === 'kilocode'
|
||||
) {
|
||||
if (providerId === 'codex' || providerId === 'gemini' || providerId === 'opencode') {
|
||||
return providerId;
|
||||
}
|
||||
|
||||
|
|
@ -116,10 +111,7 @@ export function resolveRuntimeProviderId(
|
|||
}
|
||||
|
||||
export function resolveTeamProviderId(providerId: TeamProviderId | undefined): TeamProviderId {
|
||||
return providerId === 'codex' ||
|
||||
providerId === 'gemini' ||
|
||||
providerId === 'opencode' ||
|
||||
providerId === 'kilocode'
|
||||
return providerId === 'codex' || providerId === 'gemini' || providerId === 'opencode'
|
||||
? providerId
|
||||
: 'anthropic';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1311,8 +1311,6 @@ function getProviderRuntimeFailureLabel(providerId: TeamProviderId): string {
|
|||
return 'Gemini runtime';
|
||||
case 'opencode':
|
||||
return 'OpenCode runtime';
|
||||
case 'kilocode':
|
||||
return 'KiloCode runtime';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16571,17 +16569,6 @@ export class TeamProvisioningService {
|
|||
? Array.from(new Set(providerModelChecks.map((check) => check.modelId)))
|
||||
: selectedModelIds;
|
||||
|
||||
if (providerId === 'kilocode') {
|
||||
const kilocodeConnection =
|
||||
await this.providerConnectionService.getConnectionInfo('kilocode');
|
||||
if (!kilocodeConnection.apiKeyConfigured) {
|
||||
blockingMessages.push(
|
||||
'KiloCode: API key not configured. Set KILO_API_KEY or add it in Provider Settings.'
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (providerId === 'opencode') {
|
||||
const adapter = this.getOpenCodeRuntimeAdapter();
|
||||
if (!adapter) {
|
||||
|
|
|
|||
|
|
@ -14,13 +14,7 @@ import type {
|
|||
TeamProvisioningSupportDiagnostic,
|
||||
} from '@shared/types';
|
||||
|
||||
export const TEAM_RUNTIME_PROVIDER_IDS = [
|
||||
'anthropic',
|
||||
'codex',
|
||||
'gemini',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
] as const;
|
||||
export const TEAM_RUNTIME_PROVIDER_IDS = ['anthropic', 'codex', 'gemini', 'opencode'] as const;
|
||||
|
||||
export type TeamRuntimeProviderId = (typeof TEAM_RUNTIME_PROVIDER_IDS)[number];
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,5 @@ export const ProviderBrandLogo = ({
|
|||
return <GeminiBrandLogo className={className} />;
|
||||
case 'opencode':
|
||||
return <OpenCodeBrandLogo className={className} />;
|
||||
case 'kilocode':
|
||||
return <OpenCodeBrandLogo className={className} />;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -443,8 +443,6 @@ function getProviderLabel(providerId: CliProviderId): string {
|
|||
return 'Gemini';
|
||||
case 'opencode':
|
||||
return 'OpenCode (200+ models)';
|
||||
case 'kilocode':
|
||||
return 'KiloCode';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,6 @@ export function getDashboardRateLimitsForProvider(
|
|||
return getAnthropicDashboardRateLimits(provider);
|
||||
case 'gemini':
|
||||
case 'opencode':
|
||||
case 'kilocode':
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const ProviderCapabilityCardSkeleton = ({
|
|||
providerId,
|
||||
displayName,
|
||||
}: {
|
||||
providerId: 'anthropic' | 'codex' | 'gemini' | 'opencode' | 'kilocode';
|
||||
providerId: 'anthropic' | 'codex' | 'gemini' | 'opencode';
|
||||
displayName: string;
|
||||
}): React.JSX.Element => {
|
||||
const { t } = useAppTranslation('extensions');
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ import type { CodexRuntimeStatus } from '@features/codex-runtime-installer/contr
|
|||
import type { CliProviderAuthMode, CliProviderId, CliProviderStatus } from '@shared/types';
|
||||
import type { ApiKeyEntry } from '@shared/types/extensions';
|
||||
|
||||
type ApiKeyProviderId = 'anthropic' | 'codex' | 'gemini' | 'kilocode';
|
||||
type ApiKeyProviderId = 'anthropic' | 'codex' | 'gemini';
|
||||
type PendingConnectionAction = 'auto' | 'oauth' | 'chatgpt' | 'api_key' | 'compatible' | null;
|
||||
|
||||
interface ConnectionMethodCardOption {
|
||||
|
|
@ -98,7 +98,7 @@ interface Props {
|
|||
const API_KEY_PROVIDER_CONFIG: Record<
|
||||
ApiKeyProviderId,
|
||||
{
|
||||
envVarName: 'ANTHROPIC_API_KEY' | 'OPENAI_API_KEY' | 'GEMINI_API_KEY' | 'KILO_API_KEY';
|
||||
envVarName: 'ANTHROPIC_API_KEY' | 'OPENAI_API_KEY' | 'GEMINI_API_KEY';
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
@ -129,14 +129,6 @@ const API_KEY_PROVIDER_CONFIG: Record<
|
|||
'Use `GEMINI_API_KEY` for the Gemini API backend. CLI SDK and ADC do not require it.',
|
||||
placeholder: 'AIza...',
|
||||
},
|
||||
kilocode: {
|
||||
envVarName: 'KILO_API_KEY',
|
||||
name: 'KiloCode API Key',
|
||||
title: 'API key',
|
||||
description:
|
||||
'Use your KiloCode API key to authenticate with the KiloCode gateway and load available models.',
|
||||
placeholder: 'kc-...',
|
||||
},
|
||||
};
|
||||
|
||||
const API_KEY_PROVIDER_TRANSLATION_KEYS = {
|
||||
|
|
@ -159,7 +151,7 @@ const API_KEY_PROVIDER_TRANSLATION_KEYS = {
|
|||
placeholder: 'providerRuntime.apiKey.providers.gemini.placeholder',
|
||||
},
|
||||
} as const satisfies Record<
|
||||
Exclude<ApiKeyProviderId, 'kilocode'>,
|
||||
ApiKeyProviderId,
|
||||
{
|
||||
name: string;
|
||||
title: string;
|
||||
|
|
@ -173,12 +165,7 @@ const ANTHROPIC_COMPATIBLE_AUTH_TOKEN_NAME = 'Anthropic-compatible Auth Token';
|
|||
const FIRST_PARTY_ANTHROPIC_HOSTS = new Set(['api.anthropic.com', 'api-staging.anthropic.com']);
|
||||
|
||||
function isApiKeyProviderId(providerId: CliProviderId): providerId is ApiKeyProviderId {
|
||||
return (
|
||||
providerId === 'anthropic' ||
|
||||
providerId === 'codex' ||
|
||||
providerId === 'gemini' ||
|
||||
providerId === 'kilocode'
|
||||
);
|
||||
return providerId === 'anthropic' || providerId === 'codex' || providerId === 'gemini';
|
||||
}
|
||||
|
||||
function isCodexRuntimeInstalling(
|
||||
|
|
@ -256,8 +243,6 @@ function getConnectionDescription(
|
|||
return t('providerRuntime.connection.descriptions.gemini');
|
||||
case 'opencode':
|
||||
return t('providerRuntime.connection.descriptions.opencode');
|
||||
case 'kilocode':
|
||||
return 'KiloCode uses an API key for authentication with the KiloCode gateway.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,8 +259,6 @@ function getRuntimeDescription(
|
|||
return t('providerRuntime.runtime.descriptions.gemini');
|
||||
case 'opencode':
|
||||
return t('providerRuntime.runtime.descriptions.opencode');
|
||||
case 'kilocode':
|
||||
return 'KiloCode uses its own managed runtime host. Configure an API key to use the KiloCode gateway.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,10 +291,6 @@ function getAuthModeDescription(
|
|||
}
|
||||
}
|
||||
|
||||
if (providerId === 'kilocode' && authMode === 'api_key') {
|
||||
return 'Use a KiloCode API key for gateway access.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
@ -1047,10 +1026,9 @@ export const ProviderRuntimeSettingsDialog = ({
|
|||
? selectedProvider.providerId
|
||||
: null;
|
||||
const apiKeyConfig = apiKeyProviderId ? API_KEY_PROVIDER_CONFIG[apiKeyProviderId] : null;
|
||||
const apiKeyTranslationKeys =
|
||||
apiKeyProviderId && apiKeyProviderId !== 'kilocode'
|
||||
? API_KEY_PROVIDER_TRANSLATION_KEYS[apiKeyProviderId]
|
||||
: null;
|
||||
const apiKeyTranslationKeys = apiKeyProviderId
|
||||
? API_KEY_PROVIDER_TRANSLATION_KEYS[apiKeyProviderId]
|
||||
: null;
|
||||
const apiKeyDisplayConfig = apiKeyTranslationKeys
|
||||
? {
|
||||
title: t(apiKeyTranslationKeys.title),
|
||||
|
|
@ -1058,7 +1036,7 @@ export const ProviderRuntimeSettingsDialog = ({
|
|||
name: t(apiKeyTranslationKeys.name),
|
||||
placeholder: t(apiKeyTranslationKeys.placeholder),
|
||||
}
|
||||
: apiKeyConfig;
|
||||
: null;
|
||||
const showApiKeyForm =
|
||||
selectedProvider &&
|
||||
isApiKeyProviderId(selectedProvider.providerId) &&
|
||||
|
|
|
|||
|
|
@ -124,8 +124,6 @@ function getProviderLabel(providerId: CliProviderId): string {
|
|||
return 'Gemini';
|
||||
case 'opencode':
|
||||
return 'OpenCode (200+ models)';
|
||||
case 'kilocode':
|
||||
return 'KiloCode';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -411,7 +411,6 @@ export const DateGroupedSessions = memo((): React.JSX.Element => {
|
|||
codex: 0,
|
||||
gemini: 0,
|
||||
opencode: 0,
|
||||
kilocode: 0,
|
||||
};
|
||||
|
||||
for (const session of searchedSessions) {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ const PROVIDER_LABELS: Record<TeamProviderId, string> = {
|
|||
codex: 'Codex',
|
||||
gemini: 'Gemini',
|
||||
opencode: 'OpenCode',
|
||||
kilocode: 'KiloCode',
|
||||
};
|
||||
|
||||
function getProviderLabel(providerId: TeamProviderId): string {
|
||||
|
|
|
|||
|
|
@ -28,24 +28,14 @@ const CODEX_PROVIDER_INSTALL_REFRESH_ATTEMPTS = 3;
|
|||
const CODEX_PROVIDER_INSTALL_REFRESH_RETRY_DELAY_MS = 700;
|
||||
|
||||
export const MULTIMODEL_PROVIDER_IDS: CliProviderId[] = isGeminiUiFrozen()
|
||||
? ['anthropic', 'codex', 'opencode', 'kilocode']
|
||||
: ['anthropic', 'codex', 'gemini', 'opencode', 'kilocode'];
|
||||
const MULTIMODEL_PROVIDER_HYDRATION_IDS: CliProviderId[] = isGeminiUiFrozen()
|
||||
? ['anthropic', 'codex', 'opencode']
|
||||
: ['anthropic', 'codex', 'gemini', 'opencode'];
|
||||
const MULTIMODEL_PROVIDER_ID_SET = new Set<CliProviderId>(MULTIMODEL_PROVIDER_IDS);
|
||||
const MULTIMODEL_PROVIDER_HYDRATION_ID_SET = new Set<CliProviderId>(
|
||||
MULTIMODEL_PROVIDER_HYDRATION_IDS
|
||||
);
|
||||
|
||||
function isActiveMultimodelProviderId(providerId: CliProviderId): boolean {
|
||||
return MULTIMODEL_PROVIDER_ID_SET.has(providerId);
|
||||
}
|
||||
|
||||
function isHydratableMultimodelProviderId(providerId: CliProviderId): boolean {
|
||||
return MULTIMODEL_PROVIDER_HYDRATION_ID_SET.has(providerId);
|
||||
}
|
||||
|
||||
export function createLoadingMultimodelCliStatus(): CliInstallationStatus {
|
||||
const providers: CliProviderStatus[] = MULTIMODEL_PROVIDER_IDS.map((providerId) => ({
|
||||
providerId,
|
||||
|
|
@ -269,7 +259,7 @@ export function getIncompleteMultimodelProviderIds(
|
|||
return status.providers
|
||||
.filter(
|
||||
(provider) =>
|
||||
isHydratableMultimodelProviderId(provider.providerId) &&
|
||||
isActiveMultimodelProviderId(provider.providerId) &&
|
||||
!isHydratedMultimodelProviderStatus(provider)
|
||||
)
|
||||
.map((provider) => provider.providerId);
|
||||
|
|
@ -285,7 +275,7 @@ export function getModelOnlyFallbackProviderIds(
|
|||
return status.providers
|
||||
.filter(
|
||||
(provider) =>
|
||||
isHydratableMultimodelProviderId(provider.providerId) &&
|
||||
isActiveMultimodelProviderId(provider.providerId) &&
|
||||
isModelOnlyFallbackProviderStatus(provider)
|
||||
)
|
||||
.map((provider) => provider.providerId);
|
||||
|
|
@ -303,7 +293,7 @@ export function reconcileMultimodelProviderLoading(
|
|||
const providersById = new Map(
|
||||
status.providers.map((provider) => [provider.providerId, provider])
|
||||
);
|
||||
return MULTIMODEL_PROVIDER_HYDRATION_IDS.reduce<Partial<Record<CliProviderId, boolean>>>(
|
||||
return MULTIMODEL_PROVIDER_IDS.reduce<Partial<Record<CliProviderId, boolean>>>(
|
||||
(nextLoading, providerId) => {
|
||||
const provider = providersById.get(providerId);
|
||||
return {
|
||||
|
|
@ -575,9 +565,7 @@ function isMultimodelCliStatus(
|
|||
function hasActiveProviderStatusLoading(
|
||||
providerLoading: Partial<Record<CliProviderId, boolean>>
|
||||
): boolean {
|
||||
return MULTIMODEL_PROVIDER_HYDRATION_IDS.some(
|
||||
(providerId) => providerLoading[providerId] === true
|
||||
);
|
||||
return MULTIMODEL_PROVIDER_IDS.some((providerId) => providerLoading[providerId] === true);
|
||||
}
|
||||
|
||||
function getAuthenticatedProvider(providers: CliProviderStatus[]): CliProviderStatus | null {
|
||||
|
|
@ -616,8 +604,6 @@ function getProviderDisplayName(providerId: CliProviderId): string {
|
|||
return 'Gemini';
|
||||
case 'opencode':
|
||||
return 'OpenCode (200+ models)';
|
||||
case 'kilocode':
|
||||
return 'KiloCode';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -771,7 +757,7 @@ export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstalle
|
|||
: createLoadingMultimodelCliStatus();
|
||||
const shouldMarkIncompleteProvidersLoading = hydrateProviders || providerStatusMode === 'defer';
|
||||
const providerLoading = Object.fromEntries(
|
||||
MULTIMODEL_PROVIDER_HYDRATION_IDS.map((providerId) => [
|
||||
MULTIMODEL_PROVIDER_IDS.map((providerId) => [
|
||||
providerId,
|
||||
shouldMarkIncompleteProvidersLoading &&
|
||||
initialStatus.installed &&
|
||||
|
|
@ -822,14 +808,14 @@ export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstalle
|
|||
|
||||
const nextCliStatus = mergeCliStatusPreservingHydratedProviders(state.cliStatus, metadata);
|
||||
const nextProviderLoading = Object.fromEntries(
|
||||
MULTIMODEL_PROVIDER_HYDRATION_IDS.map((providerId) => [
|
||||
MULTIMODEL_PROVIDER_IDS.map((providerId) => [
|
||||
providerId,
|
||||
!isHydratedMultimodelProviderStatus(
|
||||
nextCliStatus.providers.find((provider) => provider.providerId === providerId)
|
||||
),
|
||||
])
|
||||
) as Partial<Record<CliProviderId, boolean>>;
|
||||
pendingProviderIds = MULTIMODEL_PROVIDER_HYDRATION_IDS.filter(
|
||||
pendingProviderIds = MULTIMODEL_PROVIDER_IDS.filter(
|
||||
(providerId) => nextProviderLoading[providerId] === true
|
||||
);
|
||||
const nextAuthState = isMultimodelCliStatus(nextCliStatus)
|
||||
|
|
@ -881,7 +867,7 @@ export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstalle
|
|||
try {
|
||||
if (hydrateProviders) {
|
||||
await Promise.allSettled(
|
||||
MULTIMODEL_PROVIDER_HYDRATION_IDS.map((providerId) =>
|
||||
MULTIMODEL_PROVIDER_IDS.map((providerId) =>
|
||||
get().fetchCliProviderStatus(providerId, {
|
||||
silent: false,
|
||||
epoch,
|
||||
|
|
@ -925,7 +911,7 @@ export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstalle
|
|||
});
|
||||
if (status.installed) {
|
||||
for (const provider of status.providers) {
|
||||
if (!isHydratableMultimodelProviderId(provider.providerId)) {
|
||||
if (!isActiveMultimodelProviderId(provider.providerId)) {
|
||||
continue;
|
||||
}
|
||||
void get().fetchCliProviderStatus(provider.providerId, {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ const TEAM_PROVIDER_LABELS: Record<SupportedProviderId, string> = {
|
|||
codex: 'Codex',
|
||||
gemini: 'Gemini',
|
||||
opencode: 'OpenCode',
|
||||
kilocode: 'KiloCode',
|
||||
};
|
||||
|
||||
const ANTHROPIC_ALIAS_LABELS = {
|
||||
|
|
@ -141,7 +140,6 @@ const TEAM_PROVIDER_MODEL_OPTIONS: Record<SupportedProviderId, readonly TeamProv
|
|||
},
|
||||
],
|
||||
opencode: [{ value: '', label: 'Default', badgeLabel: 'Default' }],
|
||||
kilocode: [{ value: '', label: 'Default', badgeLabel: 'Default' }],
|
||||
};
|
||||
|
||||
const TEAM_PROVIDER_MODEL_ORDER: Record<SupportedProviderId, Map<string, number>> = {
|
||||
|
|
@ -151,9 +149,6 @@ const TEAM_PROVIDER_MODEL_ORDER: Record<SupportedProviderId, Map<string, number>
|
|||
opencode: new Map(
|
||||
TEAM_PROVIDER_MODEL_OPTIONS.opencode.map((option, index) => [option.value, index])
|
||||
),
|
||||
kilocode: new Map(
|
||||
TEAM_PROVIDER_MODEL_OPTIONS.kilocode.map((option, index) => [option.value, index])
|
||||
),
|
||||
};
|
||||
|
||||
function getKnownTeamProviderModelOption(
|
||||
|
|
@ -342,9 +337,6 @@ export function getTeamModelBadgeLabel(
|
|||
if (providerId === 'opencode') {
|
||||
return getTeamModelLabel(trimmed) ?? trimmed;
|
||||
}
|
||||
if (providerId === 'kilocode') {
|
||||
return getTeamModelLabel(trimmed) ?? trimmed;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export type CliPlatform =
|
|||
|
||||
export type CliFlavor = 'claude' | 'agent_teams_orchestrator';
|
||||
|
||||
export type CliProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode' | 'kilocode';
|
||||
export type CliProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode';
|
||||
export type CliProviderAuthMode = 'auto' | 'oauth' | 'chatgpt' | 'api_key';
|
||||
export const CLI_PROVIDER_STATUS_DEFERRED_MESSAGE = 'Provider status will refresh when needed.';
|
||||
|
||||
|
|
|
|||
|
|
@ -966,7 +966,7 @@ export interface TeamViewSnapshot {
|
|||
}
|
||||
|
||||
export type EffortLevel = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
||||
export type TeamProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode' | 'kilocode';
|
||||
export type TeamProviderId = 'anthropic' | 'codex' | 'gemini' | 'opencode';
|
||||
export type TeamProviderBackendId =
|
||||
| 'auto'
|
||||
| 'adapter'
|
||||
|
|
|
|||
|
|
@ -3,13 +3,7 @@ import { parseOpenCodeQualifiedModelRef } from './opencodeModelRef';
|
|||
import type { TeamProviderId } from '@shared/types';
|
||||
|
||||
export function isTeamProviderId(value: unknown): value is TeamProviderId {
|
||||
return (
|
||||
value === 'anthropic' ||
|
||||
value === 'codex' ||
|
||||
value === 'gemini' ||
|
||||
value === 'opencode' ||
|
||||
value === 'kilocode'
|
||||
);
|
||||
return value === 'anthropic' || value === 'codex' || value === 'gemini' || value === 'opencode';
|
||||
}
|
||||
|
||||
export function normalizeOptionalTeamProviderId(value: unknown): TeamProviderId | undefined {
|
||||
|
|
@ -39,15 +33,6 @@ export function inferTeamProviderIdFromModel(
|
|||
return 'opencode';
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.startsWith('kilocode/') ||
|
||||
normalizedWithoutExtendedContextSuffix.startsWith('kilocode/') ||
|
||||
normalized.startsWith('kilo/') ||
|
||||
normalizedWithoutExtendedContextSuffix.startsWith('kilo/')
|
||||
) {
|
||||
return 'kilocode';
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.startsWith('gpt-') ||
|
||||
normalized.startsWith('codex') ||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,6 @@ describe('CliInstallerService', () => {
|
|||
'anthropic',
|
||||
'codex',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
]);
|
||||
expect(openCodeStatus).toMatchObject({
|
||||
displayName: 'OpenCode (200+ models)',
|
||||
|
|
@ -336,7 +335,6 @@ describe('CliInstallerService', () => {
|
|||
'anthropic',
|
||||
'codex',
|
||||
'opencode',
|
||||
'kilocode',
|
||||
]);
|
||||
expect(status.authLoggedIn).toBe(false);
|
||||
expect(status.authMethod).toBeNull();
|
||||
|
|
@ -382,7 +380,7 @@ describe('CliInstallerService', () => {
|
|||
expect(resolveInteractiveShellEnvBestEffortMock).not.toHaveBeenCalled();
|
||||
expect(status.authStatusChecking).toBe(false);
|
||||
expect(status.authLoggedIn).toBe(false);
|
||||
expect(status.providers).toHaveLength(4);
|
||||
expect(status.providers).toHaveLength(3);
|
||||
expect(
|
||||
status.providers.every(
|
||||
(provider) => provider.statusMessage === 'Provider status will refresh when needed.'
|
||||
|
|
@ -1215,12 +1213,13 @@ describe('CliInstallerService', () => {
|
|||
createTestProviderStatus('codex', false, null),
|
||||
createTestProviderStatus('opencode', false, null),
|
||||
]);
|
||||
await vi.waitFor(() => {
|
||||
const latest = service.getLatestStatusSnapshot();
|
||||
expect(latest?.authStatusChecking).toBe(false);
|
||||
expect(latest?.authLoggedIn).toBe(true);
|
||||
expect(latest?.authMethod).toBe('oauth_token');
|
||||
});
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
const latest = service.getLatestStatusSnapshot();
|
||||
expect(latest?.authStatusChecking).toBe(false);
|
||||
expect(latest?.authLoggedIn).toBe(true);
|
||||
expect(latest?.authMethod).toBe('oauth_token');
|
||||
expect(status.authStatusChecking).toBe(true);
|
||||
expect(status.authLoggedIn).toBe(false);
|
||||
expect(status.providers.every((provider) => provider.statusMessage === 'Checking...')).toBe(
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ describe('ProviderConnectionService', () => {
|
|||
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',
|
||||
|
|
@ -137,7 +136,6 @@ describe('ProviderConnectionService', () => {
|
|||
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(() => {
|
||||
|
|
@ -170,12 +168,6 @@ describe('ProviderConnectionService', () => {
|
|||
} 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 () => {
|
||||
|
|
@ -647,126 +639,6 @@ describe('ProviderConnectionService', () => {
|
|||
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');
|
||||
|
|
|
|||
|
|
@ -2696,7 +2696,7 @@ describe('CLI status visibility during completed install state', () => {
|
|||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(host.textContent).toContain('Providers: 1/4 connected');
|
||||
expect(host.textContent).toContain('Providers: 1/3 connected');
|
||||
expect(host.textContent).toContain('5h left');
|
||||
expect(host.textContent).toContain('1w left');
|
||||
expect(host.textContent).toContain('resets');
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { CodexAccountSnapshotDto } from '@features/codex-account/contracts';
|
||||
import type { CliInstallationStatus } from '@shared/types';
|
||||
import type { SkillCatalogItem } from '@shared/types/extensions';
|
||||
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
||||
|
||||
interface StoreState {
|
||||
fetchSkillsCatalog: ReturnType<typeof vi.fn>;
|
||||
|
|
@ -560,7 +559,7 @@ describe('SkillsPanel', () => {
|
|||
});
|
||||
|
||||
expect(host.textContent).toContain(
|
||||
'Shared skills in `.claude`, `.cursor`, and `.agents` are available to Anthropic, Codex, OpenCode (200+ models), and KiloCode.'
|
||||
'Shared skills in `.claude`, `.cursor`, and `.agents` are available to Anthropic, Codex, and OpenCode (200+ models).'
|
||||
);
|
||||
expect(host.textContent).toContain('Codex only');
|
||||
|
||||
|
|
|
|||
|
|
@ -62,49 +62,6 @@ vi.mock('@sentry/electron/main', () => sentryNoOp);
|
|||
vi.mock('@sentry/electron/renderer', () => sentryNoOp);
|
||||
vi.mock('@sentry/react', () => sentryNoOp);
|
||||
|
||||
function createInMemoryStorage(): Storage {
|
||||
const values = new Map<string, string>();
|
||||
|
||||
return {
|
||||
get length() {
|
||||
return values.size;
|
||||
},
|
||||
clear() {
|
||||
values.clear();
|
||||
},
|
||||
getItem(key: string) {
|
||||
return values.get(key) ?? null;
|
||||
},
|
||||
key(index: number) {
|
||||
return Array.from(values.keys())[index] ?? null;
|
||||
},
|
||||
removeItem(key: string) {
|
||||
values.delete(key);
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
values.set(key, String(value));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function hasStorageApi(value: unknown): value is Storage {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof (value as Storage).getItem === 'function' &&
|
||||
typeof (value as Storage).setItem === 'function' &&
|
||||
typeof (value as Storage).removeItem === 'function' &&
|
||||
typeof (value as Storage).clear === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasStorageApi(globalThis.localStorage)) {
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
configurable: true,
|
||||
value: createInMemoryStorage(),
|
||||
});
|
||||
}
|
||||
|
||||
// Mock HOME for tests that need a predictable home path. It must be writable:
|
||||
// some services persist state in best-effort background writes after a test has
|
||||
// already reset path overrides.
|
||||
|
|
|
|||
Loading…
Reference in a new issue