mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 22:41:26 +02:00
- New providers gemini-deep-research + gemini-deep-research-max on the Interactions API (preview-04-2026). Submit/poll split, tier parameter selects between standard (~minutes, $1–3) and max (up to 60 min, $3–7). - Parser matches the real response shape: flat `outputs` array of thought|text|image items, url_citation annotations without title, `usage.total_input_tokens` / `total_output_tokens`. - Route generalisation: /v1/research/async accepts `provider` with default 'openai-deep-research' (backward compatible) and dispatches to the right submit/poll pair. - New internal service-to-service endpoint /v1/internal/research/async gated by X-Service-Key + X-User-Id for credit accounting. Enables mana-ai to drive deep-research jobs on the mission owner's wallet without requiring a user JWT. - Pricing: 300 credits (standard) / 1500 credits (max). Conservative markup over the ~$3/$7 ceiling so the first runs can't surprise us. - Docs: AGENT_PROVIDER_IDS + pricing + env map + auto-router stay in sync; CLAUDE.md Phase 3b now current; API_KEYS.md references the new providers under GOOGLE_GENAI_API_KEY. Verified with a real smoke test against the Gemini API: submit + poll both succeed, completed response parsed cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
117 lines
4.3 KiB
TypeScript
117 lines
4.3 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 { createInternalResearchRoutes } from './routes/internal-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). Callers pass the target user
|
|
// in X-User-Id so credit accounting still lands on the right wallet.
|
|
app.use('/api/v1/internal/*', serviceAuth(config.serviceKey));
|
|
app.get('/api/v1/internal/health', (c) => c.json({ ok: true }));
|
|
app.route('/api/v1/internal/research', createInternalResearchRoutes(config, asyncStorage, credits));
|
|
|
|
// ─── Start ──────────────────────────────────────────────────
|
|
|
|
console.log(`mana-research starting on port ${config.port}...`);
|
|
|
|
export default {
|
|
port: config.port,
|
|
fetch: app.fetch,
|
|
};
|