mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 15:06:43 +02:00
Two backlog items landed in one commit because an earlier amend in a
parallel terminal dropped the initial Phase 3b commit and the BYO-keys
work was blocked on the same wiring.
openai-deep-research (async):
- New research.async_jobs table persists the OpenAI response.id, query,
reservation, and cached result/error.
- POST /v1/research/async reserves credits, submits to the Responses API
with background=true, returns a taskId. Submit failure refunds.
- GET /v1/research/async/:taskId polls upstream, commits the reservation
on completion, refunds on failure, short-circuits for terminal states.
- GET /v1/research/async lists the user's async tasks.
BYO-keys:
- research.provider_configs CRUD at /v1/provider-configs. Keys are masked
(••••last4) on read so the raw secret never re-transits to the browser.
Currently stored plaintext with a TODO for AES-GCM-256 via the shared
KEK — single call site in storage/configs.ts.decryptKey().
- New frontend route /research-lab/keys lets the user paste a key per
provider, toggle enabled, and set daily/monthly credit budgets.
- ListView grew a 🔑 link in the header.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
/**
|
|
* mana-research — Web Research Provider Orchestration
|
|
*
|
|
* Bundles search/extract/agent providers behind a unified interface.
|
|
* Phase 1: 4 search providers (SearXNG, DuckDuckGo, Brave, Tavily) with
|
|
* credits + cache + eval-run persistence.
|
|
*
|
|
* Port: 3068. See docs/plans/mana-research-service.md.
|
|
*/
|
|
|
|
import { Hono } from 'hono';
|
|
import { cors } from 'hono/cors';
|
|
import { loadConfig } from './config';
|
|
import { getDb } from './db/connection';
|
|
import { serviceErrorHandler } from '@mana/shared-hono';
|
|
import { jwtAuth } from './middleware/jwt-auth';
|
|
import { serviceAuth } from './middleware/service-auth';
|
|
import { healthRoutes } from './routes/health';
|
|
import { createSearchRoutes } from './routes/search';
|
|
import { createExtractRoutes } from './routes/extract';
|
|
import { createResearchRoutes } from './routes/research';
|
|
import { createProvidersRoutes } from './routes/providers';
|
|
import { createRunsRoutes } from './routes/runs';
|
|
import { createProviderConfigRoutes } from './routes/provider-configs';
|
|
import { buildRegistry } from './providers/registry';
|
|
import { RunStorage } from './storage/runs';
|
|
import { ConfigStorage } from './storage/configs';
|
|
import { AsyncJobStorage } from './storage/async-jobs';
|
|
import { CreditsClient } from './clients/mana-credits';
|
|
import { ManaSearchClient } from './clients/mana-search';
|
|
import { ManaLlmClient } from './clients/mana-llm';
|
|
import { initCache } from './lib/cache';
|
|
|
|
// ─── Bootstrap ──────────────────────────────────────────────
|
|
|
|
const config = loadConfig();
|
|
const db = getDb(config.databaseUrl);
|
|
|
|
initCache(config.redisUrl);
|
|
|
|
const manaSearch = new ManaSearchClient(config.manaSearchUrl);
|
|
const manaLlm = new ManaLlmClient(config.manaLlmUrl);
|
|
const credits = new CreditsClient({
|
|
baseUrl: config.manaCreditsUrl,
|
|
serviceKey: config.serviceKey,
|
|
});
|
|
|
|
const runStorage = new RunStorage(db);
|
|
const configStorage = new ConfigStorage(db);
|
|
const asyncStorage = new AsyncJobStorage(db);
|
|
const registry = buildRegistry({ manaSearch });
|
|
|
|
const executorDeps = {
|
|
credits,
|
|
configs: configStorage,
|
|
config,
|
|
};
|
|
|
|
// ─── App ────────────────────────────────────────────────────
|
|
|
|
const app = new Hono();
|
|
|
|
app.onError(serviceErrorHandler);
|
|
app.use(
|
|
'*',
|
|
cors({
|
|
origin: config.cors.origins,
|
|
credentials: true,
|
|
})
|
|
);
|
|
|
|
// Health (no auth)
|
|
app.route('/health', healthRoutes);
|
|
|
|
// Metrics stub (no auth) — will be populated in Phase 2 with prometheus-style output
|
|
app.get('/metrics', (c) => c.text('# mana-research metrics stub\n'));
|
|
|
|
// Providers catalog (no auth — callers often query this to build UIs)
|
|
app.route('/api/v1/providers', createProvidersRoutes(registry, config));
|
|
|
|
// User-facing research (JWT auth)
|
|
app.use('/api/v1/search/*', jwtAuth(config.manaAuthUrl));
|
|
app.route(
|
|
'/api/v1/search',
|
|
createSearchRoutes(registry, runStorage, executorDeps, config, manaLlm)
|
|
);
|
|
|
|
app.use('/api/v1/extract/*', jwtAuth(config.manaAuthUrl));
|
|
app.route('/api/v1/extract', createExtractRoutes(registry, runStorage, executorDeps, config));
|
|
|
|
app.use('/api/v1/research/*', jwtAuth(config.manaAuthUrl));
|
|
app.route(
|
|
'/api/v1/research',
|
|
createResearchRoutes(registry, runStorage, executorDeps, config, asyncStorage, credits)
|
|
);
|
|
|
|
app.use('/api/v1/runs/*', jwtAuth(config.manaAuthUrl));
|
|
app.route('/api/v1/runs', createRunsRoutes(runStorage));
|
|
|
|
app.use('/api/v1/provider-configs/*', jwtAuth(config.manaAuthUrl));
|
|
app.route('/api/v1/provider-configs', createProviderConfigRoutes(db));
|
|
|
|
// Service-to-service (X-Service-Key auth) — wired up in Phase 3 when mana-ai migrates
|
|
app.use('/api/v1/internal/*', serviceAuth(config.serviceKey));
|
|
app.get('/api/v1/internal/health', (c) => c.json({ ok: true }));
|
|
|
|
// ─── Start ──────────────────────────────────────────────────
|
|
|
|
console.log(`mana-research starting on port ${config.port}...`);
|
|
|
|
export default {
|
|
port: config.port,
|
|
fetch: app.fetch,
|
|
};
|