feat: productionize learning engine and classroom
This commit is contained in:
1 parent
2d04197f3f
commit
ba35adfbfa
124 files changed
+9071
-796
No files matched your search
@@ -0,0 +1,126 @@
|
||||
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 };
|
||||
}
|
||||
Reference in new issue
Block a user