blob: 69e93054ca3741960de44ade40a0d8377edad47c [file]
import {
CONNECTION_CATALOG_MAX_MODELS_PER_CONNECTION,
decodeConnectionModelId,
decodeConnectionModel,
decodeProviderType,
decodeConnectionTestSummary,
decodeConnectionVersionBasis,
RuntimePolicyDomainDecodeError,
type ConnectionVersionBasis,
type ModelDiscoverySource,
} from '@maka/core';
import type { ModelInfo, ProviderType } from '@maka/core';
import {
requireCount,
requireEntityId,
requireExactRecord,
requireRecord,
requireString,
} from './codec.js';
import { invalidProtocolFrame } from './errors.js';
import { defineOperation } from './operation-spec.js';
const EFFECT_ERRORS = [
'host_not_ready',
'host_draining',
'operation_unavailable',
'invalid_request',
'internal_failure',
'persistence_failed',
'commit_outcome_unknown',
] as const;
export const CONNECTION_EFFECT_CHANGED_DOMAINS = [
'connection',
'credential',
'network_proxy',
] as const;
export type ConnectionEffectChangedDomain = (typeof CONNECTION_EFFECT_CHANGED_DOMAINS)[number];
export const CONNECTION_EFFECT_REJECTION_REASONS = [
'connection_not_found',
'connection_disabled',
'provider_action_unavailable',
'credential_not_configured',
] as const;
export type ConnectionEffectRejectionReason = (typeof CONNECTION_EFFECT_REJECTION_REASONS)[number];
export const CONNECTION_EFFECT_FAILURE_CLASSES = [
'auth',
'timeout',
'provider_unavailable',
'network',
'invalid_response',
'unknown',
] as const;
export type ConnectionEffectFailureClass = (typeof CONNECTION_EFFECT_FAILURE_CLASSES)[number];
export interface ConnectionModelFetchInput {
readonly connectionId: string;
}
export interface ConnectionTestRunInput {
readonly connectionId: string;
readonly modelId: string | null;
}
export interface ConnectionOnboardingVerifyInput {
readonly providerType: ProviderType;
readonly apiKey: string | null;
}
export interface ConnectionOnboardingSaveInput extends ConnectionOnboardingVerifyInput {
readonly enabledModelIds: readonly string[];
}
export type ConnectionOnboardingVerifyResult =
| { readonly kind: 'verified'; readonly models: readonly ModelInfo[] }
| {
readonly kind: 'rejected';
readonly reason: 'provider_unsupported' | 'credential_not_configured' | 'slug_conflict';
}
| { readonly kind: 'failed'; readonly errorClass: ConnectionEffectFailureClass };
export type ConnectionOnboardingSaveResult =
| { readonly kind: 'saved' }
| {
readonly kind: 'rejected';
readonly reason:
| 'provider_unsupported'
| 'credential_not_configured'
| 'slug_conflict'
| 'model_unavailable';
}
| { readonly kind: 'failed'; readonly errorClass: ConnectionEffectFailureClass };
interface ConnectionEffectCommitted {
readonly kind: 'committed';
readonly catalogRevision: number;
readonly connection: ConnectionVersionBasis;
}
interface ConnectionEffectRejected {
readonly kind: 'rejected';
readonly reason: ConnectionEffectRejectionReason;
}
interface ConnectionEffectSuperseded {
readonly kind: 'superseded';
readonly changed: readonly ConnectionEffectChangedDomain[];
}
interface ConnectionEffectFailed {
readonly kind: 'failed';
readonly errorClass: ConnectionEffectFailureClass;
}
export type ConnectionModelFetchResult =
| (ConnectionEffectCommitted & {
readonly modelCount: number;
readonly source: ModelDiscoverySource;
readonly fetchedAt: number;
})
| ConnectionEffectRejected
| ConnectionEffectSuperseded
| ConnectionEffectFailed;
export type ConnectionTestProjection =
| {
readonly kind: 'verified';
readonly checkedAt: string;
readonly modelId: string;
readonly latencyMs: number;
}
| {
readonly kind: 'failed';
readonly checkedAt: string;
readonly modelId: string | null;
readonly latencyMs: number | null;
readonly statusCode: number | null;
readonly errorClass: ConnectionEffectFailureClass;
};
export type ConnectionTestRunResult =
| (ConnectionEffectCommitted & {
readonly test: ConnectionTestProjection;
})
| ConnectionEffectRejected
| ConnectionEffectSuperseded;
export const CONNECTION_EFFECT_OPERATION_SPECS = {
'connection.onboarding.save': defineOperation<
ConnectionOnboardingSaveInput,
ConnectionOnboardingSaveResult,
(typeof EFFECT_ERRORS)[number]
>({
mode: 'command',
availability: 'ready',
errors: EFFECT_ERRORS,
decodeInput: decodeConnectionOnboardingSaveInput,
decodeOutput: decodeConnectionOnboardingSaveResult,
}),
'connection.onboarding.verify': defineOperation<
ConnectionOnboardingVerifyInput,
ConnectionOnboardingVerifyResult,
(typeof EFFECT_ERRORS)[number]
>({
mode: 'command',
availability: 'ready',
errors: EFFECT_ERRORS,
decodeInput: decodeConnectionOnboardingVerifyInput,
decodeOutput: decodeConnectionOnboardingVerifyResult,
}),
'connection.models.fetch': defineOperation<
ConnectionModelFetchInput,
ConnectionModelFetchResult,
(typeof EFFECT_ERRORS)[number]
>({
mode: 'command',
availability: 'ready',
errors: EFFECT_ERRORS,
decodeInput: decodeConnectionModelFetchInput,
decodeOutput: decodeConnectionModelFetchResult,
}),
'connection.test.run': defineOperation<
ConnectionTestRunInput,
ConnectionTestRunResult,
(typeof EFFECT_ERRORS)[number]
>({
mode: 'command',
availability: 'ready',
errors: EFFECT_ERRORS,
decodeInput: decodeConnectionTestRunInput,
decodeOutput: decodeConnectionTestRunResult,
}),
} as const;
export function decodeConnectionOnboardingSaveInput(value: unknown): ConnectionOnboardingSaveInput {
const input = requireExactRecord(value, 'connection onboarding save input', [
'providerType',
'apiKey',
'enabledModelIds',
]);
const verified = decodeConnectionOnboardingVerifyInput({
providerType: input.providerType,
apiKey: input.apiKey,
});
if (
!Array.isArray(input.enabledModelIds) ||
input.enabledModelIds.length === 0 ||
input.enabledModelIds.length > CONNECTION_CATALOG_MAX_MODELS_PER_CONNECTION
) {
throw invalidProtocolFrame('Connection onboarding requires at least one enabled model');
}
const enabledModelIds = input.enabledModelIds.map((modelId) =>
decodeDomain(() => decodeConnectionModelId(modelId)),
);
if (new Set(enabledModelIds).size !== enabledModelIds.length) {
throw invalidProtocolFrame('Connection onboarding enabled models must be unique');
}
return { ...verified, enabledModelIds };
}
export function decodeConnectionOnboardingSaveResult(
value: unknown,
): ConnectionOnboardingSaveResult {
const result = requireRecord(value, 'connection onboarding save result');
if (result.kind === 'saved') {
requireExactRecord(result, 'saved connection onboarding result', ['kind']);
return { kind: 'saved' };
}
if (result.kind === 'failed') {
const failed = requireExactRecord(result, 'failed connection onboarding save result', [
'kind',
'errorClass',
]);
return { kind: 'failed', errorClass: effectFailureClass(failed.errorClass) };
}
const rejected = requireExactRecord(result, 'rejected connection onboarding save result', [
'kind',
'reason',
]);
if (
rejected.kind !== 'rejected' ||
(rejected.reason !== 'provider_unsupported' &&
rejected.reason !== 'credential_not_configured' &&
rejected.reason !== 'slug_conflict' &&
rejected.reason !== 'model_unavailable')
) {
throw invalidProtocolFrame('Invalid connection onboarding save rejection');
}
return { kind: 'rejected', reason: rejected.reason };
}
export function decodeConnectionOnboardingVerifyInput(
value: unknown,
): ConnectionOnboardingVerifyInput {
const input = requireExactRecord(value, 'connection onboarding verification input', [
'providerType',
'apiKey',
]);
return {
providerType: decodeDomain(() => decodeProviderType(input.providerType)),
apiKey:
input.apiKey === null
? null
: requireString(input.apiKey, 'connection onboarding API key', 64 * 1024),
};
}
export function decodeConnectionOnboardingVerifyResult(
value: unknown,
): ConnectionOnboardingVerifyResult {
const result = requireRecord(value, 'connection onboarding verification result');
if (result.kind === 'verified') {
const verified = requireExactRecord(result, 'verified connection onboarding result', [
'kind',
'models',
]);
if (!Array.isArray(verified.models) || verified.models.length === 0) {
throw invalidProtocolFrame('Connection onboarding models must be a non-empty array');
}
return {
kind: 'verified',
models: verified.models.map((model) => decodeDomain(() => decodeConnectionModel(model))),
};
}
if (result.kind === 'failed') {
const failed = requireExactRecord(result, 'failed connection onboarding result', [
'kind',
'errorClass',
]);
return { kind: 'failed', errorClass: effectFailureClass(failed.errorClass) };
}
const rejected = requireExactRecord(result, 'rejected connection onboarding result', [
'kind',
'reason',
]);
if (
rejected.kind !== 'rejected' ||
(rejected.reason !== 'provider_unsupported' &&
rejected.reason !== 'credential_not_configured' &&
rejected.reason !== 'slug_conflict')
) {
throw invalidProtocolFrame('Invalid connection onboarding rejection');
}
return { kind: 'rejected', reason: rejected.reason };
}
export function decodeConnectionModelFetchInput(value: unknown): ConnectionModelFetchInput {
const input = requireExactRecord(value, 'connection model fetch input', ['connectionId']);
return { connectionId: requireEntityId(input.connectionId, 'connectionId') };
}
export function decodeConnectionTestRunInput(value: unknown): ConnectionTestRunInput {
const input = requireExactRecord(value, 'connection test input', ['connectionId', 'modelId']);
return {
connectionId: requireEntityId(input.connectionId, 'connectionId'),
modelId:
input.modelId === null ? null : decodeDomain(() => decodeConnectionModelId(input.modelId)),
};
}
export function decodeConnectionModelFetchResult(value: unknown): ConnectionModelFetchResult {
const result = requireRecord(value, 'connection model fetch result');
if (result.kind === 'committed') {
const committed = requireExactRecord(result, 'connection model fetch committed result', [
'kind',
'catalogRevision',
'connection',
'modelCount',
'source',
'fetchedAt',
]);
return {
...decodeCommitted(committed, 'connection model fetch committed result'),
modelCount: boundedInteger(
committed.modelCount,
'model count',
1,
CONNECTION_CATALOG_MAX_MODELS_PER_CONNECTION,
),
source: modelDiscoverySource(committed.source),
fetchedAt: requireCount(committed.fetchedAt, 'models fetched at'),
};
}
if (result.kind === 'failed') {
const failed = requireExactRecord(result, 'connection model fetch failed result', [
'kind',
'errorClass',
]);
return { kind: 'failed', errorClass: effectFailureClass(failed.errorClass) };
}
return decodeNonEffectResult(result, 'connection model fetch result');
}
export function decodeConnectionTestRunResult(value: unknown): ConnectionTestRunResult {
const result = requireRecord(value, 'connection test result');
if (result.kind === 'committed') {
const committed = requireExactRecord(result, 'connection test committed result', [
'kind',
'catalogRevision',
'connection',
'test',
]);
return {
...decodeCommitted(committed, 'connection test committed result'),
test: decodeConnectionTestProjection(committed.test),
};
}
return decodeNonEffectResult(result, 'connection test result');
}
function decodeConnectionTestProjection(value: unknown): ConnectionTestProjection {
const projection = requireRecord(value, 'connection test projection');
if (projection.kind === 'verified') {
const verified = requireExactRecord(projection, 'verified connection test projection', [
'kind',
'checkedAt',
'modelId',
'latencyMs',
]);
return {
kind: 'verified',
checkedAt: connectionTestCheckedAt(verified.checkedAt),
modelId: decodeDomain(() => decodeConnectionModelId(verified.modelId)),
latencyMs: requireCount(verified.latencyMs, 'connection test latency'),
};
}
const failed = requireExactRecord(projection, 'failed connection test projection', [
'kind',
'checkedAt',
'modelId',
'latencyMs',
'statusCode',
'errorClass',
]);
if (failed.kind !== 'failed') throw invalidProtocolFrame('Invalid connection test projection');
return {
kind: 'failed',
checkedAt: connectionTestCheckedAt(failed.checkedAt),
modelId:
failed.modelId === null ? null : decodeDomain(() => decodeConnectionModelId(failed.modelId)),
latencyMs:
failed.latencyMs === null ? null : requireCount(failed.latencyMs, 'connection test latency'),
statusCode:
failed.statusCode === null
? null
: boundedInteger(failed.statusCode, 'connection test status code', 100, 599),
errorClass: effectFailureClass(failed.errorClass),
};
}
function connectionTestCheckedAt(value: unknown): string {
return decodeDomain(() => decodeConnectionTestSummary({ status: 'verified', checkedAt: value }))
.checkedAt;
}
function decodeCommitted(value: Record<string, unknown>, label: string): ConnectionEffectCommitted {
if (value.kind !== 'committed') throw invalidProtocolFrame(`Invalid ${label}`);
return {
kind: 'committed',
catalogRevision: requireCount(value.catalogRevision, 'connection catalog revision'),
connection: decodeDomain(() => decodeConnectionVersionBasis(value.connection)),
};
}
function decodeNonEffectResult(
value: Record<string, unknown>,
label: string,
): ConnectionEffectRejected | ConnectionEffectSuperseded {
if (value.kind === 'rejected') {
const rejected = requireExactRecord(value, label, ['kind', 'reason']);
return { kind: 'rejected', reason: rejectionReason(rejected.reason) };
}
if (value.kind === 'superseded') {
const superseded = requireExactRecord(value, label, ['kind', 'changed']);
if (
!Array.isArray(superseded.changed) ||
superseded.changed.length === 0 ||
superseded.changed.length > CONNECTION_EFFECT_CHANGED_DOMAINS.length
) {
throw invalidProtocolFrame('Invalid connection effect changed domains');
}
const changed = superseded.changed.map(effectChangedDomain);
if (new Set(changed).size !== changed.length) {
throw invalidProtocolFrame('Duplicate connection effect changed domain');
}
return { kind: 'superseded', changed };
}
throw invalidProtocolFrame(`Invalid ${label}`);
}
function rejectionReason(value: unknown): ConnectionEffectRejectionReason {
if (
typeof value === 'string' &&
(CONNECTION_EFFECT_REJECTION_REASONS as readonly string[]).includes(value)
) {
return value as ConnectionEffectRejectionReason;
}
throw invalidProtocolFrame('Invalid connection effect rejection reason');
}
function effectChangedDomain(value: unknown): ConnectionEffectChangedDomain {
if (
typeof value === 'string' &&
(CONNECTION_EFFECT_CHANGED_DOMAINS as readonly string[]).includes(value)
) {
return value as ConnectionEffectChangedDomain;
}
throw invalidProtocolFrame('Invalid connection effect changed domain');
}
function effectFailureClass(value: unknown): ConnectionEffectFailureClass {
if (
typeof value === 'string' &&
(CONNECTION_EFFECT_FAILURE_CLASSES as readonly string[]).includes(value)
) {
return value as ConnectionEffectFailureClass;
}
throw invalidProtocolFrame('Invalid connection effect failure class');
}
function modelDiscoverySource(value: unknown): ModelDiscoverySource {
if (value === 'fetched' || value === 'fallback') return value;
throw invalidProtocolFrame('Invalid model discovery source');
}
function boundedInteger(value: unknown, label: string, min: number, max: number): number {
if (!Number.isSafeInteger(value) || (value as number) < min || (value as number) > max) {
throw invalidProtocolFrame(`Invalid ${label}`);
}
return value as number;
}
function decodeDomain<T>(operation: () => T): T {
try {
return operation();
} catch (error) {
if (error instanceof RuntimePolicyDomainDecodeError) {
throw invalidProtocolFrame(error.message);
}
throw error;
}
}