blob: 371a4851854069e5277293c006cf96affae852b7 [file]
import { deriveConnectionSlug } from '@maka/core';
import type { ConnectionCatalogSnapshot } from '@maka/core';
import {
readRuntimeHostConnectionCatalog,
type RuntimeHostConnection,
} from '@maka/runtime-host/client';
import { listApiKeyOnboardableProviders } from './onboarding-catalog.js';
import type {
MakaOnboardingSurface,
ModelChoice,
OnboardingProviderEntry,
} from './pi-tui-contracts.js';
/** Adapt the TUI onboarding workflow to Host-owned verification and persistence. */
export function createRuntimeHostOnboardingSurface(
connection: RuntimeHostConnection,
): MakaOnboardingSurface {
return {
listProviders: async () => projectProviders(await readRuntimeHostConnectionCatalog(connection)),
verify: async (input) => {
try {
const result = await connection.request('connection.onboarding.verify', {
providerType: input.providerType,
apiKey: normalizedSecret(input.apiKey),
});
return result.kind === 'verified'
? { kind: 'ok', models: [...result.models] }
: { kind: 'error', text: onboardingFailureText(result) };
} catch (error) {
return { kind: 'error', text: errorText(error) };
}
},
save: async (input) => {
try {
const result = await connection.request('connection.onboarding.save', {
providerType: input.providerType,
apiKey: normalizedSecret(input.apiKey),
enabledModelIds: [...input.enabledModelIds],
});
if (result.kind !== 'saved') {
return { kind: 'error', text: onboardingFailureText(result) };
}
return {
kind: 'ok',
modelChoices: projectRuntimeHostModelChoices(
await readRuntimeHostConnectionCatalog(connection),
),
};
} catch (error) {
return { kind: 'error', text: errorText(error) };
}
},
};
}
export function projectRuntimeHostModelChoices(catalog: ConnectionCatalogSnapshot): ModelChoice[] {
const choices: ModelChoice[] = [];
for (const connection of catalog.connections) {
if (!connection.enabled) continue;
const modelsById = new Map(connection.models.map((model) => [model.id, model]));
const ids = new Set(connection.enabledModelIds);
if (catalog.defaultTarget?.connectionId === connection.connectionId) {
ids.add(catalog.defaultTarget.modelId);
}
for (const model of ids) {
choices.push({
connectionSlug: connection.slug,
connectionName: connection.name,
providerType: connection.providerType,
model,
isDefaultConnection: catalog.defaultTarget?.connectionId === connection.connectionId,
contextWindow: modelsById.get(model)?.contextWindow,
});
}
}
return choices;
}
function projectProviders(catalog: ConnectionCatalogSnapshot): OnboardingProviderEntry[] {
const bySlug = new Map(catalog.connections.map((connection) => [connection.slug, connection]));
return listApiKeyOnboardableProviders().map((provider) => {
const candidate = bySlug.get(deriveConnectionSlug(provider.providerType));
const existing = candidate?.providerType === provider.providerType ? candidate : undefined;
return {
...provider,
hasConnection: existing !== undefined,
enabledModelIds: existing ? [...existing.enabledModelIds] : [],
};
});
}
function normalizedSecret(value: string | undefined): string | null {
const secret = value?.trim() ?? '';
return secret.length === 0 ? null : secret;
}
function onboardingFailureText(input: {
readonly kind: 'rejected' | 'failed';
readonly reason?: string;
readonly errorClass?: string;
}): string {
if (input.kind === 'failed') return `Connection verification failed: ${input.errorClass}`;
switch (input.reason) {
case 'credential_not_configured':
return 'API key is required';
case 'provider_unsupported':
return 'This provider does not support API-key onboarding';
case 'slug_conflict':
return 'The provider connection name is already used by another provider';
case 'model_unavailable':
return 'The selected model is no longer available';
default:
return 'Connection onboarding was rejected';
}
}
function errorText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}