MCPcopy
hub / github.com/promptfoo/promptfoo / startServer

Function startServer

src/server/server.ts:372–569  ·  view source on GitHub ↗
(
  port = getDefaultPort(),
  browserBehavior: BrowserBehavior = BrowserBehavior.ASK,
)

Source from the content-addressed store, hash-verified

370}
371
372export async function startServer(
373 port = getDefaultPort(),
374 browserBehavior: BrowserBehavior = BrowserBehavior.ASK,
375) {
376 const app = createApp();
377
378 const httpServer = http.createServer(app);
379 const io = new SocketIOServer(httpServer, {
380 cors: {
381 origin: '*',
382 },
383 });
384
385 await runDbMigrations();
386
387 const watcher = setupSignalWatcher(() => {
388 const handleSignalUpdate = async () => {
389 // A new eval signal can change /api/prompts output. Invalidate first — before this handler
390 // awaits any DB lookups and regardless of which emit branches below run — so every signal
391 // drops the cache and the next /api/prompts request re-reads it from the DB.
392 promptCacheService.invalidate();
393
394 const signal = readSignalFile();
395 // A coalesced signal can carry several scoped updates (back-to-back eval saves in the
396 // debounce window), an unscoped "refresh latest" component, and a delete component.
397 const scopedUpdateIds = updateEvalIds(signal);
398
399 // Mutations can happen in a separate CLI process, so clear this server's process-local caches
400 // before serving the next request. A delete-all or an unscoped update (whose latest eval is
401 // not known here) can touch any eval's cached count, so clear them all; otherwise scope the
402 // clear to exactly the evals this signal updates and/or deletes.
403 if (
404 (hasDeleteComponent(signal) && isAllEvalsDeleted(signal.deletedEvalIds)) ||
405 hasUnscopedUpdate(signal)
406 ) {
407 invalidateEvaluationCache();
408 } else {
409 invalidateEvaluationCaches([...scopedUpdateIds, ...(signal.deletedEvalIds ?? [])]);
410 }
411
412 // Emit each component independently (not else-if) so no coalesced refresh is lost.
413 if (hasDeleteComponent(signal)) {
414 io.emit('update', { deletedEvalIds: signal.deletedEvalIds ?? [] });
415 }
416
417 // Emit one refresh per surviving scoped eval so a client pinned to any of them updates.
418 if (scopedUpdateIds.length > 0) {
419 const updatedEvals = await Promise.all(scopedUpdateIds.map((id) => Eval.findById(id)));
420 for (const updatedEval of updatedEvals) {
421 if (updatedEval) {
422 logger.debug(
423 `Emitting update for eval: ${updatedEval.config?.description || updatedEval.id}`,
424 );
425 io.emit('update', { evalId: updatedEval.id });
426 }
427 // Else: a scoped update whose eval no longer exists (e.g. it won the debounce race
428 // against its own delete). Emit nothing — clients reconcile on the next signal.
429 }

Callers 7

server.test.tsFile · 0.90
viewCommandFunction · 0.90
evalSetupCommandFunction · 0.90
redteamReportCommandFunction · 0.90
initCommandFunction · 0.90
redteamSetupCommandFunction · 0.90
mainFunction · 0.90

Calls 11

getDefaultPortFunction · 0.90
runDbMigrationsFunction · 0.90
setupSignalWatcherFunction · 0.90
openBrowserFunction · 0.90
createAppFunction · 0.85
handleSignalUpdateFunction · 0.85
onMethod · 0.80
emitMethod · 0.80
listenMethod · 0.80
errorMethod · 0.80
latestMethod · 0.45

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…