127 lines
4.9 KiB
TypeScript
127 lines
4.9 KiB
TypeScript
import { constants } from 'node:fs';
|
|
import { access, mkdir, writeFile, unlink } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { nanoid } from 'nanoid';
|
|
import { COURSE_FRAMEWORK_DIR } from '@/lib/course-framework/store';
|
|
import { COURSE_MANIFEST_DIR } from '@/lib/course-manifest-repo/store';
|
|
import { COURSEWARES_DIR } from '@/lib/courseware-repo/store';
|
|
import { getServerProviders, getServerTTSProviders } from '@/lib/server/provider-config';
|
|
import { CLASSROOM_JOBS_DIR, CLASSROOMS_DIR } from '@/lib/server/classroom-storage';
|
|
import { configuredLearningRuntimeToken } from './auth';
|
|
import { configuredLearningEngineRuntimeBaseUrl } from './ops-engine-control';
|
|
import { resolveDeploymentRole } from '@/lib/config/deployment-role';
|
|
|
|
export const LEARNING_ENGINE_SERVICE = 'openmaic-learning-engine';
|
|
export const LEARNING_ENGINE_API_VERSION = 'v1';
|
|
|
|
export type LearningReadinessCheck = {
|
|
ok: boolean;
|
|
detail?: string;
|
|
};
|
|
|
|
function dataRoot(): string {
|
|
return path.resolve(process.env.LEARNING_DATA_DIR?.trim() || path.join(process.cwd(), 'data'));
|
|
}
|
|
|
|
export function learningPersistenceDirectories(): Record<string, string> {
|
|
const root = dataRoot();
|
|
return {
|
|
classrooms: process.env.CLASSROOM_DATA_DIR || CLASSROOMS_DIR,
|
|
classroomJobs: process.env.CLASSROOM_JOBS_DIR || CLASSROOM_JOBS_DIR,
|
|
courseFrameworks: process.env.COURSE_FRAMEWORK_DIR || COURSE_FRAMEWORK_DIR,
|
|
courseManifests: process.env.COURSE_MANIFEST_DIR || COURSE_MANIFEST_DIR,
|
|
coursewares: process.env.COURSEWARE_DATA_DIR || COURSEWARES_DIR,
|
|
coursewareBundles:
|
|
process.env.COURSEWARE_BUNDLE_DIR || path.join(root, 'courseware-bundles'),
|
|
learningPackages:
|
|
process.env.LEARNING_COURSE_PACKAGE_DIR ||
|
|
process.env.MAKELORE_COURSE_PACKAGE_DIR ||
|
|
path.join(root, 'makelore-packages'),
|
|
learningCourses:
|
|
process.env.LEARNING_COURSE_STORE_DIR ||
|
|
process.env.MAKELORE_COURSE_STORE_DIR ||
|
|
path.join(root, 'makelore-courses'),
|
|
};
|
|
}
|
|
|
|
async function verifyWritableDirectory(directory: string): Promise<void> {
|
|
await mkdir(directory, { recursive: true });
|
|
await access(directory, constants.R_OK | constants.W_OK);
|
|
const probe = path.join(directory, `.learning-ready-${process.pid}-${nanoid(6)}.tmp`);
|
|
await writeFile(probe, 'ready', { flag: 'wx' });
|
|
await unlink(probe);
|
|
}
|
|
|
|
function configuredOrigin(name: string): boolean {
|
|
const raw = process.env[name]?.trim();
|
|
if (!raw) return false;
|
|
try {
|
|
const url = new URL(raw);
|
|
return (
|
|
(url.protocol === 'https:' || url.protocol === 'http:') &&
|
|
!url.username &&
|
|
!url.password &&
|
|
!url.search &&
|
|
!url.hash &&
|
|
(url.pathname === '' || url.pathname === '/')
|
|
);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function inspectLearningReadiness(): Promise<{
|
|
ready: boolean;
|
|
checks: Record<string, LearningReadinessCheck>;
|
|
}> {
|
|
const role = resolveDeploymentRole(
|
|
process.env.OPENMAIC_DEPLOYMENT_ROLE ?? process.env.NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE,
|
|
process.env.NODE_ENV,
|
|
);
|
|
const checks: Record<string, LearningReadinessCheck> = role === 'ops'
|
|
? {
|
|
worksIdentityOrigin: configuredOrigin('WORKS_SQUARE_API_BASE_URL')
|
|
? { ok: true }
|
|
: { ok: false, detail: 'WORKS_SQUARE_API_BASE_URL is not configured as an origin' },
|
|
opsPublicOrigin: configuredOrigin('OPS_PUBLIC_ORIGIN')
|
|
? { ok: true }
|
|
: { ok: false, detail: 'OPS_PUBLIC_ORIGIN is not configured as an origin' },
|
|
engineControlEndpoint: configuredLearningEngineRuntimeBaseUrl()
|
|
? { ok: true }
|
|
: {
|
|
ok: false,
|
|
detail: 'LEARNING_ENGINE_BASE_URL or COURSE_PUBLISH_SERVER_BASE_URL is not configured',
|
|
},
|
|
runtimeToken: configuredLearningRuntimeToken()
|
|
? { ok: true }
|
|
: { ok: false, detail: 'LEARNING_ENGINE_TOKEN is not configured' },
|
|
}
|
|
: {
|
|
runtimeToken: configuredLearningRuntimeToken()
|
|
? { ok: true }
|
|
: { ok: false, detail: 'LEARNING_ENGINE_TOKEN is not configured' },
|
|
llmProvider:
|
|
Object.keys(getServerProviders()).length > 0
|
|
? { ok: true }
|
|
: { ok: false, detail: 'No server-managed LLM provider is configured' },
|
|
ttsProvider:
|
|
Object.values(getServerTTSProviders()).some((provider) => !provider.disabled)
|
|
? { ok: true }
|
|
: { ok: false, detail: 'No enabled server-managed TTS provider is configured' },
|
|
};
|
|
|
|
for (const [name, directory] of Object.entries(learningPersistenceDirectories())) {
|
|
try {
|
|
await verifyWritableDirectory(directory);
|
|
checks[`storage.${name}`] = { ok: true };
|
|
} catch (error) {
|
|
checks[`storage.${name}`] = {
|
|
ok: false,
|
|
detail: error instanceof Error ? error.message : 'Storage is not writable',
|
|
};
|
|
}
|
|
}
|
|
|
|
return { ready: Object.values(checks).every((check) => check.ok), checks };
|
|
}
|