@konneal/engine 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/admin.d.ts +1 -0
  2. package/dist/ask-47RNGK2R.js +12 -0
  3. package/dist/chunk-3OXSQH7Y.js +1852 -0
  4. package/dist/chunk-6GOSMLRH.js +2781 -0
  5. package/dist/{chunk-EHJEELVB.js → chunk-LNSDBEKS.js} +1 -1
  6. package/dist/{chunk-35ODH64W.js → chunk-Q327B27J.js} +33 -0
  7. package/dist/{chunk-OCNLV7Q7.js → chunk-Q6LI4T7M.js} +6 -1
  8. package/dist/{chunk-ROF3Q7UC.js → chunk-SN3ANQ3Y.js} +2 -2
  9. package/dist/chunk-VJZLVU3S.js +64 -0
  10. package/dist/{chunk-CAEHIVG5.js → chunk-WGXATDXY.js} +1 -1
  11. package/dist/codecs.d.ts +3 -3
  12. package/dist/completion.d.ts +1 -1
  13. package/dist/config.d.ts +9 -0
  14. package/dist/faithfulness.d.ts +1 -0
  15. package/dist/mcp-proto.d.ts +25 -0
  16. package/dist/mcp.d.ts +4 -0
  17. package/dist/openapi-surface.gen.d.ts +9 -0
  18. package/dist/openapi-types.d.ts +2141 -0
  19. package/dist/profile.gen.d.ts +1 -0
  20. package/dist/prompts/system.md +1 -0
  21. package/dist/quota.d.ts +4 -1
  22. package/dist/search-OMPBMZT4.js +11 -0
  23. package/dist/tablecontext.d.ts +7 -0
  24. package/dist/verdict-parse.d.ts +5 -0
  25. package/dist/worker_mcp/src/index.js +3 -3
  26. package/dist/worker_public/src/config.js +4 -2
  27. package/dist/worker_public/src/index.js +1193 -4932
  28. package/dist/worker_public/src/profile.js +1 -1
  29. package/dist/worker_public/src/refusal.js +2 -2
  30. package/dist/worker_public/src/requestScope.js +3 -3
  31. package/docs/spec-api.md +27 -13
  32. package/package.json +12 -3
  33. package/profile/prompts.yaml +3 -0
  34. package/workers/shared/router.ts +23 -16
  35. package/workers/worker_public/migrations/0014_usage_cache.sql +5 -0
  36. package/workers/worker_public/openapi.yaml +1169 -0
  37. package/workers/worker_public/prompts/system.md +1 -0
  38. package/workers/worker_public/schema.sql +3 -1
  39. package/workers/worker_public/src/admin.ts +51 -7
  40. package/workers/worker_public/src/ai.ts +10 -2
  41. package/workers/worker_public/src/ask.ts +94 -22
  42. package/workers/worker_public/src/codecs.ts +35 -10
  43. package/workers/worker_public/src/completion.ts +24 -1
  44. package/workers/worker_public/src/config.ts +10 -0
  45. package/workers/worker_public/src/faithfulness.ts +10 -17
  46. package/workers/worker_public/src/grader.ts +2 -2
  47. package/workers/worker_public/src/index.ts +106 -58
  48. package/workers/worker_public/src/lib/router.ts +1 -1
  49. package/workers/worker_public/src/mcp-proto.ts +71 -0
  50. package/workers/worker_public/src/mcp.ts +47 -0
  51. package/workers/worker_public/src/openapi-surface.gen.ts +318 -0
  52. package/workers/worker_public/src/pipeline.ts +4 -2
  53. package/workers/worker_public/src/profile.gen.ts +1 -0
  54. package/workers/worker_public/src/projects.ts +4 -2
  55. package/workers/worker_public/src/quota.ts +4 -2
  56. package/workers/worker_public/src/research.ts +55 -3
  57. package/workers/worker_public/src/tablecontext.ts +13 -2
  58. package/workers/worker_public/src/verdict-parse.ts +60 -0
@@ -1,4 +1,4 @@
1
- import { MODELS, datasetsFor, SUGGESTIONS, roleModel } from "./config";
1
+ import { MODELS, datasetsFor, SUGGESTIONS, STARTERS, roleModel } from "./config";
2
2
  export { setProfile } from "./profile.ts";
3
3
  import { retrieve } from "./pipeline";
4
4
  import type { Hit } from "./pipeline";
@@ -19,18 +19,25 @@ export type { Env };
19
19
  import { json, err, corsHeaders, withCors, readJson, authenticate, type ApiKey } from "./lib/http";
20
20
 
21
21
  import { handleSearch } from "./search";
22
- import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey } from "./admin";
22
+ import { handleEnrich, handleSectionUnit, handleCaption, handleVectors, handleJudge, handleCreateKey, handleListKeys, handleRevokeKey, handleKeyUsage } from "./admin";
23
23
  import { handleResearch } from "./research";
24
24
  import { handleAsk } from "./ask";
25
-
26
- // One named handler per HTTP route, declared in ROUTES below and
27
- // dispatched by lib/router.ts's matchRoute. Adding a route = one entry +
28
- // its handler; entry order is irrelevant (every pattern is
29
- // segment-exact). Dual-published routes (/api for the browser, /v1 for
30
- // keyed integrators) point at the SAME handler — the tier split is the
31
- // handler's, derived from the path. Mirrored in docs/spec-api.md.
32
-
33
- import { matchRoute, type RouteContext, type Route } from "./lib/router";
25
+ import { handleMcp } from "./mcp";
26
+
27
+ // The HTTP surface is the OpenAPI document (workers/worker_public/openapi.yaml):
28
+ // scripts/gen-openapi-routes.mjs generates openapi-surface.gen.ts from it,
29
+ // and OPENAPI_HANDLERS below binds every operation to its handler. The
30
+ // Record<OpenApiOperationId, …> type makes the binding exhaustive at
31
+ // typecheck, and tests/openapi-surface.test.ts pins the bijection. Adding
32
+ // an endpoint = the yaml entry + one binding + regenerate. Dual-published
33
+ // routes (/api for the browser, /v1 for keyed integrators) bind the SAME
34
+ // handler — the tier split is the handler's, derived from the path.
35
+ // Non-API routes (pages, unit assets, rendered documents) live in
36
+ // INFRA_ROUTES. Mirrored in docs/spec-api.md.
37
+
38
+ import { matchRoute, routeMatchesPath, type RouteContext, type Route, type RouteHandler } from "./lib/router";
39
+ import { OPENAPI_SURFACE } from "./openapi-surface.gen";
40
+ import type { OpenApiOperationId } from "./openapi-surface.gen";
34
41
  import { P } from "./profile.ts";
35
42
 
36
43
  async function serveIndexPage(c: RouteContext): Promise<Response> {
@@ -97,7 +104,7 @@ async function getSharedRoute(c: RouteContext): Promise<Response> {
97
104
 
98
105
  async function datasetsRoute(c: RouteContext): Promise<Response> {
99
106
  const session = await sessionFrom(c.req, c.env as any);
100
- return json({ datasets: datasetsFor(session), suggestions: SUGGESTIONS() }, 200, corsHeaders(c.req));
107
+ return json({ datasets: datasetsFor(session), suggestions: SUGGESTIONS(), starters: STARTERS() }, 200, corsHeaders(c.req));
101
108
  }
102
109
 
103
110
  async function healthRoute(c: RouteContext): Promise<Response> {
@@ -120,6 +127,12 @@ async function tierFor(c: RouteContext): Promise<{ tier: "anon" | "key" | "membe
120
127
  return { tier, key };
121
128
  }
122
129
 
130
+ async function mcpRoute(c: RouteContext): Promise<Response> {
131
+ const t = await tierFor(c);
132
+ if (t instanceof Response) return t;
133
+ return withCors(await handleMcp(c.env, c.ctx, c.req, t.tier, t.key), corsHeaders(c.req));
134
+ }
135
+
123
136
  async function askRoute(c: RouteContext): Promise<Response> {
124
137
  const t = await tierFor(c);
125
138
  if (t instanceof Response) return t;
@@ -137,14 +150,18 @@ async function adminStatsRoute(c: RouteContext): Promise<Response> {
137
150
  if (!env.ADMIN_TOKEN) return err(501, "admin_disabled", "ADMIN_TOKEN secret is not configured");
138
151
  const auth = req.headers.get("authorization") ?? "";
139
152
  if (auth !== `Bearer ${env.ADMIN_TOKEN}`) return err(401, "unauthorized", "Invalid admin token");
140
- const [byDay, byModel, feedback, convCount] = await Promise.all([
153
+ const [byDay, byModel, feedback, convCount, cacheMix, durations] = await Promise.all([
141
154
  env.DB.prepare("SELECT day, tier, COUNT(*) as n, SUM(ok) as ok FROM queries WHERE day >= date('now','-7 days') GROUP BY day, tier ORDER BY day DESC").all(),
142
155
  env.DB.prepare("SELECT model, SUM(requests) as requests FROM spend WHERE day >= date('now','-7 days') GROUP BY model ORDER BY requests DESC").all(),
143
156
  env.DB.prepare("SELECT rating, COUNT(*) as n FROM feedback GROUP BY rating").all(),
144
157
  env.DB.prepare("SELECT COUNT(*) as n FROM conversations").first(),
158
+ env.DB.prepare("SELECT COALESCE(cache, 'miss') AS cache, COUNT(*) AS n FROM queries WHERE day >= date('now','-7 days') AND route = 'ask' GROUP BY cache").all(),
159
+ env.DB.prepare("SELECT duration_ms FROM queries WHERE day >= date('now','-7 days') AND route = 'ask' AND duration_ms IS NOT NULL").all(),
145
160
  ]);
161
+ const ds = (durations.results as any[]).map((r) => r.duration_ms as number).sort((a, b) => a - b);
162
+ const pct = (q: number) => (ds.length ? ds[Math.min(ds.length - 1, Math.floor(q * ds.length))] : null);
146
163
  const totalQueries = (byDay.results as any[]).reduce((a, r) => a + (r.n || 0), 0) || 0;
147
- const totalOk = (byDay.results as any[]).reduce((a, r) => a + (r.ok_count || 0), 0) || 0;
164
+ const totalOk = (byDay.results as any[]).reduce((a, r) => a + (r.ok || 0), 0) || 0;
148
165
  const errorRate = totalQueries > 0 ? (((totalQueries - totalOk) / totalQueries) * 100).toFixed(1) : "0";
149
166
  ctx.waitUntil(env.DB.batch([
150
167
  env.DB.prepare("DELETE FROM queries WHERE day < date('now','-90 days')"),
@@ -156,6 +173,8 @@ async function adminStatsRoute(c: RouteContext): Promise<Response> {
156
173
  queries_by_day: byDay.results,
157
174
  spend_by_model: byModel.results,
158
175
  feedback: feedback.results,
176
+ cache_mix_7d: cacheMix.results,
177
+ latency_ms: ds.length ? { n: ds.length, p50: pct(0.5), p95: pct(0.95) } : null,
159
178
  conversations: (convCount as any)?.n ?? 0,
160
179
  error_rate_pct: errorRate,
161
180
  index_version: env.INDEX_VERSION,
@@ -190,7 +209,7 @@ async function absenceRoute(c: RouteContext): Promise<Response> {
190
209
  enumerated: { model_nodes: nodes.length, smart_model_chunks: chunks?.n ?? 0 },
191
210
  matches: matches.slice(0, 20),
192
211
  verdict: matches.length === 0 ? "absent" : "present",
193
- scope: `the model plane of ${standard} (all model nodes) — the enumeration is exhaustive over that scope; prose outside the modeled families is not claimed`,
212
+ scope: `the machine-readable model of ${standard} (all model nodes) — the enumeration is exhaustive over that scope; prose outside the modeled families is not claimed`,
194
213
  });
195
214
  } catch (e) {
196
215
  return err(502, "absence_failed", String(e).slice(0, 200));
@@ -375,54 +394,78 @@ async function researchRoute(c: RouteContext): Promise<Response> {
375
394
  return handleResearch(c.env, c.ctx, c.req, session);
376
395
  }
377
396
 
378
- export const ROUTES: Route[] = [
397
+ // The non-API routes: the HTML pages, the unit-keyed figure assets and
398
+ // the rendered publication documents. Everything else is generated from
399
+ // the OpenAPI document.
400
+ const INFRA_ROUTES: Route[] = [
379
401
  { method: "GET", pattern: "/", handler: serveIndexPage },
380
402
  { method: "GET", pattern: "/api/", handler: serveIndexPage },
381
403
  { method: "GET", pattern: "/index.html", handler: serveIndexPage },
382
- { method: "GET", pattern: "/auth/login", handler: (c) => handleLogin(c.env as any, c.req) },
383
- { method: "GET", pattern: "/auth/callback", handler: (c) => handleCallback(c.env as any, c.req) },
384
- { method: "GET", pattern: "/auth/me", handler: async (c) => withCors(await handleMe(c.env as any, c.req), corsHeaders(c.req)) },
385
- { method: "GET", pattern: "/auth/logout", handler: (c) => handleLogout(c.env as any, c.req) },
386
- { method: "POST", pattern: "/auth/logout", handler: (c) => handleLogout(c.env as any, c.req) },
387
- { method: "*", pattern: "/api/conversations", handler: conversationsRoute },
388
- { method: "*", pattern: "/api/memories", handler: memoriesRoute },
389
- { method: "*", pattern: "/api/projects", handler: projectsRoute },
390
- { method: "*", pattern: "/api/projects/:id/files", handler: projectFilesRoute },
391
- { method: "DELETE", pattern: "/api/project-files/:id", handler: projectFilesRoute },
392
- { method: "*", pattern: "/api/memories/:id", handler: memoriesRoute },
393
- { method: "*", pattern: "/api/conversations/:id", handler: conversationsRoute },
394
- { method: "POST", pattern: "/api/conversations/:id/messages", handler: appendMessageRoute },
395
- { method: "POST", pattern: "/api/conversations/:id/share", handler: shareRoute },
396
- { method: "GET", pattern: "/api/shared/:slug", handler: getSharedRoute },
397
- { method: "GET", pattern: "/api/datasets", handler: datasetsRoute },
398
- { method: "GET", pattern: "/health", handler: healthRoute },
399
- { method: "GET", pattern: "/v1/admin/stats", handler: adminStatsRoute },
400
- { method: "POST", pattern: "/api/ask", handler: askRoute },
401
- { method: "POST", pattern: "/v1/ask", handler: askRoute },
402
- { method: "POST", pattern: "/api/absence", handler: absenceRoute },
403
- { method: "POST", pattern: "/v1/absence", handler: absenceRoute },
404
- { method: "POST", pattern: "/api/verify", handler: verifyRoute },
405
- { method: "POST", pattern: "/v1/verify", handler: verifyRoute },
406
- { method: "POST", pattern: "/api/lane", handler: laneRoute },
407
- { method: "POST", pattern: "/v1/lane", handler: laneRoute },
408
- { method: "POST", pattern: "/api/search", handler: searchRoute },
409
- { method: "POST", pattern: "/v1/search", handler: searchRoute },
410
- { method: "POST", pattern: "/api/feedback", handler: feedbackRoute },
411
- { method: "POST", pattern: "/admin/enrich", handler: (c) => handleEnrich(c.env, c.ctx, c.req) },
412
- { method: "POST", pattern: "/v1/admin/enrich", handler: (c) => handleEnrich(c.env, c.ctx, c.req) },
413
- { method: "POST", pattern: "/admin/section", handler: (c) => handleSectionUnit(c.env, c.ctx, c.req) },
414
- { method: "POST", pattern: "/v1/admin/section", handler: (c) => handleSectionUnit(c.env, c.ctx, c.req) },
415
- { method: "POST", pattern: "/admin/vectors", handler: (c) => handleVectors(c.env, c.req) },
416
- { method: "POST", pattern: "/admin/caption", handler: (c) => handleCaption(c.env, c.req) },
417
404
  { method: "GET", pattern: "/assets/*", handler: unitAssetRoute },
418
405
  { method: "GET", pattern: "/docs/*", handler: docsRoute },
419
- { method: "POST", pattern: "/api/research", handler: researchRoute },
420
- { method: "POST", pattern: "/v1/research", handler: researchRoute },
421
- { method: "POST", pattern: "/admin/judge", handler: (c) => handleJudge(c.env, c.req) },
422
- { method: "POST", pattern: "/v1/admin/judge", handler: (c) => handleJudge(c.env, c.req) },
423
- { method: "POST", pattern: "/v1/admin/keys", handler: (c) => handleCreateKey(c.env, c.req) },
424
- { method: "GET", pattern: "/v1/admin/keys", handler: (c) => handleListKeys(c.env, c.req) },
425
- { method: "DELETE", pattern: "/v1/admin/keys/:id", handler: (c) => handleRevokeKey(c.env, c.req, c.params.id) },
406
+ ];
407
+
408
+ const OPENAPI_HANDLERS: Record<OpenApiOperationId, RouteHandler> = {
409
+ askAnonymous: askRoute,
410
+ askKeyed: askRoute,
411
+ search: searchRoute,
412
+ searchKeyed: searchRoute,
413
+ absence: absenceRoute,
414
+ absenceKeyed: absenceRoute,
415
+ verify: verifyRoute,
416
+ verifyKeyed: verifyRoute,
417
+ research: researchRoute,
418
+ researchKeyed: researchRoute,
419
+ laneQuery: laneRoute,
420
+ laneKeyed: laneRoute,
421
+ mcp: mcpRoute,
422
+ feedback: feedbackRoute,
423
+ datasets: datasetsRoute,
424
+ keyUsage: async (c) => withCors(await handleKeyUsage(c.env, c.req), corsHeaders(c.req)),
425
+ health: healthRoute,
426
+ authLogin: (c) => handleLogin(c.env as any, c.req),
427
+ authCallback: (c) => handleCallback(c.env as any, c.req),
428
+ authMe: async (c) => withCors(await handleMe(c.env as any, c.req), corsHeaders(c.req)),
429
+ authLogout: (c) => handleLogout(c.env as any, c.req),
430
+ authLogoutLink: (c) => handleLogout(c.env as any, c.req),
431
+ listConversations: conversationsRoute,
432
+ createConversation: conversationsRoute,
433
+ getConversation: conversationsRoute,
434
+ renameConversation: conversationsRoute,
435
+ deleteConversation: conversationsRoute,
436
+ appendMessage: appendMessageRoute,
437
+ shareConversation: shareRoute,
438
+ getShared: getSharedRoute,
439
+ listMemories: memoriesRoute,
440
+ createMemory: memoriesRoute,
441
+ deleteMemory: memoriesRoute,
442
+ listProjects: projectsRoute,
443
+ createProject: projectsRoute,
444
+ deleteProject: projectsRoute,
445
+ listProjectFiles: projectFilesRoute,
446
+ attachProjectFile: projectFilesRoute,
447
+ detachProjectFile: projectFilesRoute,
448
+ adminStats: adminStatsRoute,
449
+ adminListKeys: (c) => handleListKeys(c.env, c.req),
450
+ adminCreateKey: (c) => handleCreateKey(c.env, c.req),
451
+ adminRevokeKey: (c) => handleRevokeKey(c.env, c.req, c.params.id),
452
+ adminEnrich: (c) => handleEnrich(c.env, c.ctx, c.req),
453
+ adminEnrichAlias: (c) => handleEnrich(c.env, c.ctx, c.req),
454
+ adminSection: (c) => handleSectionUnit(c.env, c.ctx, c.req),
455
+ adminSectionAlias: (c) => handleSectionUnit(c.env, c.ctx, c.req),
456
+ adminVectors: (c) => handleVectors(c.env, c.req),
457
+ adminCaption: (c) => handleCaption(c.env, c.req),
458
+ adminJudge: (c) => handleJudge(c.env, c.req),
459
+ adminJudgeAlias: (c) => handleJudge(c.env, c.req),
460
+ };
461
+
462
+ export const ROUTES: Route[] = [
463
+ ...INFRA_ROUTES,
464
+ ...OPENAPI_SURFACE.map((r) => ({
465
+ method: r.method,
466
+ pattern: r.pattern,
467
+ handler: OPENAPI_HANDLERS[r.operationId],
468
+ })),
426
469
  ];
427
470
 
428
471
  export default {
@@ -437,6 +480,11 @@ export default {
437
480
  if (matched) {
438
481
  return matched.route.handler({ env, req, ctx, url, path, params: matched.params });
439
482
  }
483
+ // the surface declares methods per path; a path that exists under
484
+ // another method is a 405, not a 404
485
+ if (ROUTES.some((r) => routeMatchesPath(r.pattern, path))) {
486
+ return err(405, "method_not_allowed", `The path is served, but not with ${req.method}`);
487
+ }
440
488
  return err(404, "not_found", "Unknown route");
441
489
  },
442
490
  };
@@ -1,4 +1,4 @@
1
1
  // The router lives with the shared cross-worker modules
2
2
  // (workers/shared/router.ts) — re-exported for worker_public's imports.
3
- export { matchRoute } from "../../../shared/router.ts";
3
+ export { matchRoute, routeMatchesPath } from "../../../shared/router.ts";
4
4
  export type { RouteContext, Route, RouteHandler } from "../../../shared/router.ts";
@@ -0,0 +1,71 @@
1
+ // The MCP protocol surface (JSON-RPC 2.0, streamable HTTP 2025-06-18),
2
+ // dependency-free so plain node can load it for unit tests — the
3
+ // route adapter (mcp.ts) owns the handler wiring.
4
+ export const PROTOCOL_VERSION = "2025-06-18";
5
+
6
+ export interface McpTool {
7
+ name: string;
8
+ description: string;
9
+ inputSchema: { type: "object"; properties: Record<string, unknown>; required: string[] };
10
+ }
11
+
12
+ export const TOOLS: McpTool[] = [
13
+ {
14
+ name: "ask",
15
+ description: "Ask the corpus a question; returns a citation-grounded answer with the passages it rests on.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ query: { type: "string", description: "The question (1-8000 chars)" },
20
+ lang: { type: "string", description: "Answer language hint (e.g. en, fr)" },
21
+ },
22
+ required: ["query"],
23
+ },
24
+ },
25
+ {
26
+ name: "retrieve",
27
+ description: "Retrieve the top passages for a query (hybrid dense + metadata steering, reranked).",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ query: { type: "string" },
32
+ k: { type: "number", description: "Hits to return (default 5)" },
33
+ },
34
+ required: ["query"],
35
+ },
36
+ },
37
+ ];
38
+
39
+ export type McpResult =
40
+ | { ok: true; result: unknown }
41
+ | { ok: true; accepted: true }
42
+ | { ok: false; code: number; message: string };
43
+
44
+ /** Dispatch one JSON-RPC request; `callTool` adapts a tool call to the
45
+ * real handlers (the route adapter's job). */
46
+ export function dispatch(
47
+ method: string | null,
48
+ params: any,
49
+ callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>,
50
+ ): Promise<McpResult> {
51
+ switch (method) {
52
+ case "initialize":
53
+ return Promise.resolve({ ok: true, result: { protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: { name: "rag", version: "1.0.0" } } });
54
+ case "notifications/initialized":
55
+ case "ping":
56
+ return Promise.resolve({ ok: true, accepted: true });
57
+ case "tools/list":
58
+ return Promise.resolve({ ok: true, result: { tools: TOOLS } });
59
+ case "tools/call": {
60
+ const name = typeof params?.name === "string" ? params.name : "";
61
+ if (!TOOLS.some((t) => t.name === name)) {
62
+ return Promise.resolve({ ok: false, code: -32602, message: `unknown tool: ${name}` });
63
+ }
64
+ return callTool(name, params?.arguments ?? {}).then(
65
+ (payload) => ({ ok: true, result: { content: [{ type: "text", text: JSON.stringify(payload) }] } }) as McpResult,
66
+ );
67
+ }
68
+ default:
69
+ return Promise.resolve({ ok: false, code: -32601, message: `method not found: ${method}` });
70
+ }
71
+ }
@@ -0,0 +1,47 @@
1
+ // The public-audience MCP server (streamable HTTP): the same
2
+ // ask/retrieve surfaces as /v1, behind the same API-key tiering, for
3
+ // agent ecosystems. A thin adapter by design — each tool call is an
4
+ // internal Request to the exported route handler, so MCP can never
5
+ // drift from the API contract; the protocol dispatch lives in
6
+ // mcp-proto.ts (dependency-free, unit-tested). The internal-audience
7
+ // server federates both indexes and lives in its own worker, never here.
8
+ import { json, readJson, type ApiKey } from "./lib/http";
9
+ import { P } from "./profile.ts";
10
+ import { dispatch } from "./mcp-proto.ts";
11
+ import type { Env } from "./env.ts";
12
+ import type { Background } from "./ports/runtime.ts";
13
+
14
+ export async function handleMcp(
15
+ env: Env,
16
+ // the port type, not the provider token — the handlers cast at their edge
17
+ ctx: Background,
18
+ req: Request,
19
+ tier: "anon" | "key" | "member",
20
+ key: ApiKey | null,
21
+ ): Promise<Response> {
22
+ const body = await readJson(req);
23
+ const method = typeof body?.method === "string" ? body.method : null;
24
+ const id = body?.id ?? null;
25
+
26
+ const out = await dispatch(method, body?.params, async (name, args) => {
27
+ const inner = new Request("https://internal/mcp", {
28
+ method: "POST",
29
+ headers: { "content-type": "application/json" },
30
+ // stream:false forces the JSON lane (anon defaults to SSE)
31
+ body: JSON.stringify({ ...args, stream: false }),
32
+ });
33
+ // deferred so plain node can load mcp-proto without the handlers'
34
+ // .md prompt imports, which only the bundler resolves
35
+ const res = name === "ask"
36
+ ? await (await import("./ask")).handleAsk(env, ctx as any, inner, tier, key)
37
+ : await (await import("./search")).handleSearch(env, ctx as any, inner, tier, key);
38
+ return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
39
+ });
40
+
41
+ if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
42
+ if (out.ok) {
43
+ if ((out.result as any)?.serverInfo) (out.result as any).serverInfo.name = `${P().publisher.id}-rag`;
44
+ return json({ jsonrpc: "2.0", id, result: out.result });
45
+ }
46
+ return json({ jsonrpc: "2.0", id, error: { code: out.code, message: out.message } });
47
+ }
@@ -0,0 +1,318 @@
1
+ // GENERATED from workers/worker_public/openapi.yaml — do not edit.
2
+ // Regenerate: node scripts/gen-openapi-routes.mjs
3
+ export interface OpenApiRoute {
4
+ method: string
5
+ pattern: string
6
+ operationId: string
7
+ }
8
+
9
+ export type OpenApiOperationId =
10
+ | "askAnonymous"
11
+ | "askKeyed"
12
+ | "search"
13
+ | "searchKeyed"
14
+ | "absence"
15
+ | "absenceKeyed"
16
+ | "verify"
17
+ | "verifyKeyed"
18
+ | "research"
19
+ | "researchKeyed"
20
+ | "mcp"
21
+ | "datasets"
22
+ | "keyUsage"
23
+ | "health"
24
+ | "listConversations"
25
+ | "createConversation"
26
+ | "getConversation"
27
+ | "renameConversation"
28
+ | "deleteConversation"
29
+ | "appendMessage"
30
+ | "shareConversation"
31
+ | "getShared"
32
+ | "listMemories"
33
+ | "createMemory"
34
+ | "deleteMemory"
35
+ | "listProjects"
36
+ | "createProject"
37
+ | "deleteProject"
38
+ | "listProjectFiles"
39
+ | "attachProjectFile"
40
+ | "detachProjectFile"
41
+ | "laneQuery"
42
+ | "laneKeyed"
43
+ | "feedback"
44
+ | "adminEnrich"
45
+ | "adminEnrichAlias"
46
+ | "adminSection"
47
+ | "adminSectionAlias"
48
+ | "adminVectors"
49
+ | "adminCaption"
50
+ | "adminJudge"
51
+ | "adminJudgeAlias"
52
+ | "adminRevokeKey"
53
+ | "authMe"
54
+ | "authLogin"
55
+ | "authCallback"
56
+ | "authLogout"
57
+ | "authLogoutLink"
58
+ | "adminStats"
59
+ | "adminListKeys"
60
+ | "adminCreateKey"
61
+
62
+ export const OPENAPI_SURFACE: readonly (Omit<OpenApiRoute, "operationId"> & { operationId: OpenApiOperationId })[] = [
63
+ {
64
+ "method": "POST",
65
+ "pattern": "/api/ask",
66
+ "operationId": "askAnonymous"
67
+ },
68
+ {
69
+ "method": "POST",
70
+ "pattern": "/v1/ask",
71
+ "operationId": "askKeyed"
72
+ },
73
+ {
74
+ "method": "POST",
75
+ "pattern": "/api/search",
76
+ "operationId": "search"
77
+ },
78
+ {
79
+ "method": "POST",
80
+ "pattern": "/v1/search",
81
+ "operationId": "searchKeyed"
82
+ },
83
+ {
84
+ "method": "POST",
85
+ "pattern": "/api/absence",
86
+ "operationId": "absence"
87
+ },
88
+ {
89
+ "method": "POST",
90
+ "pattern": "/v1/absence",
91
+ "operationId": "absenceKeyed"
92
+ },
93
+ {
94
+ "method": "POST",
95
+ "pattern": "/api/verify",
96
+ "operationId": "verify"
97
+ },
98
+ {
99
+ "method": "POST",
100
+ "pattern": "/v1/verify",
101
+ "operationId": "verifyKeyed"
102
+ },
103
+ {
104
+ "method": "POST",
105
+ "pattern": "/api/research",
106
+ "operationId": "research"
107
+ },
108
+ {
109
+ "method": "POST",
110
+ "pattern": "/v1/research",
111
+ "operationId": "researchKeyed"
112
+ },
113
+ {
114
+ "method": "POST",
115
+ "pattern": "/mcp",
116
+ "operationId": "mcp"
117
+ },
118
+ {
119
+ "method": "GET",
120
+ "pattern": "/api/datasets",
121
+ "operationId": "datasets"
122
+ },
123
+ {
124
+ "method": "GET",
125
+ "pattern": "/v1/usage",
126
+ "operationId": "keyUsage"
127
+ },
128
+ {
129
+ "method": "GET",
130
+ "pattern": "/health",
131
+ "operationId": "health"
132
+ },
133
+ {
134
+ "method": "GET",
135
+ "pattern": "/api/conversations",
136
+ "operationId": "listConversations"
137
+ },
138
+ {
139
+ "method": "POST",
140
+ "pattern": "/api/conversations",
141
+ "operationId": "createConversation"
142
+ },
143
+ {
144
+ "method": "GET",
145
+ "pattern": "/api/conversations/:id",
146
+ "operationId": "getConversation"
147
+ },
148
+ {
149
+ "method": "PATCH",
150
+ "pattern": "/api/conversations/:id",
151
+ "operationId": "renameConversation"
152
+ },
153
+ {
154
+ "method": "DELETE",
155
+ "pattern": "/api/conversations/:id",
156
+ "operationId": "deleteConversation"
157
+ },
158
+ {
159
+ "method": "POST",
160
+ "pattern": "/api/conversations/:id/messages",
161
+ "operationId": "appendMessage"
162
+ },
163
+ {
164
+ "method": "POST",
165
+ "pattern": "/api/conversations/:id/share",
166
+ "operationId": "shareConversation"
167
+ },
168
+ {
169
+ "method": "GET",
170
+ "pattern": "/api/shared/:slug",
171
+ "operationId": "getShared"
172
+ },
173
+ {
174
+ "method": "GET",
175
+ "pattern": "/api/memories",
176
+ "operationId": "listMemories"
177
+ },
178
+ {
179
+ "method": "POST",
180
+ "pattern": "/api/memories",
181
+ "operationId": "createMemory"
182
+ },
183
+ {
184
+ "method": "DELETE",
185
+ "pattern": "/api/memories/:id",
186
+ "operationId": "deleteMemory"
187
+ },
188
+ {
189
+ "method": "GET",
190
+ "pattern": "/api/projects",
191
+ "operationId": "listProjects"
192
+ },
193
+ {
194
+ "method": "POST",
195
+ "pattern": "/api/projects",
196
+ "operationId": "createProject"
197
+ },
198
+ {
199
+ "method": "DELETE",
200
+ "pattern": "/api/projects/:id",
201
+ "operationId": "deleteProject"
202
+ },
203
+ {
204
+ "method": "GET",
205
+ "pattern": "/api/projects/:id/files",
206
+ "operationId": "listProjectFiles"
207
+ },
208
+ {
209
+ "method": "POST",
210
+ "pattern": "/api/projects/:id/files",
211
+ "operationId": "attachProjectFile"
212
+ },
213
+ {
214
+ "method": "DELETE",
215
+ "pattern": "/api/project-files/:id",
216
+ "operationId": "detachProjectFile"
217
+ },
218
+ {
219
+ "method": "POST",
220
+ "pattern": "/api/lane",
221
+ "operationId": "laneQuery"
222
+ },
223
+ {
224
+ "method": "POST",
225
+ "pattern": "/v1/lane",
226
+ "operationId": "laneKeyed"
227
+ },
228
+ {
229
+ "method": "POST",
230
+ "pattern": "/api/feedback",
231
+ "operationId": "feedback"
232
+ },
233
+ {
234
+ "method": "POST",
235
+ "pattern": "/v1/admin/enrich",
236
+ "operationId": "adminEnrich"
237
+ },
238
+ {
239
+ "method": "POST",
240
+ "pattern": "/admin/enrich",
241
+ "operationId": "adminEnrichAlias"
242
+ },
243
+ {
244
+ "method": "POST",
245
+ "pattern": "/v1/admin/section",
246
+ "operationId": "adminSection"
247
+ },
248
+ {
249
+ "method": "POST",
250
+ "pattern": "/admin/section",
251
+ "operationId": "adminSectionAlias"
252
+ },
253
+ {
254
+ "method": "POST",
255
+ "pattern": "/admin/vectors",
256
+ "operationId": "adminVectors"
257
+ },
258
+ {
259
+ "method": "POST",
260
+ "pattern": "/admin/caption",
261
+ "operationId": "adminCaption"
262
+ },
263
+ {
264
+ "method": "POST",
265
+ "pattern": "/v1/admin/judge",
266
+ "operationId": "adminJudge"
267
+ },
268
+ {
269
+ "method": "POST",
270
+ "pattern": "/admin/judge",
271
+ "operationId": "adminJudgeAlias"
272
+ },
273
+ {
274
+ "method": "DELETE",
275
+ "pattern": "/v1/admin/keys/:id",
276
+ "operationId": "adminRevokeKey"
277
+ },
278
+ {
279
+ "method": "GET",
280
+ "pattern": "/auth/me",
281
+ "operationId": "authMe"
282
+ },
283
+ {
284
+ "method": "GET",
285
+ "pattern": "/auth/login",
286
+ "operationId": "authLogin"
287
+ },
288
+ {
289
+ "method": "GET",
290
+ "pattern": "/auth/callback",
291
+ "operationId": "authCallback"
292
+ },
293
+ {
294
+ "method": "POST",
295
+ "pattern": "/auth/logout",
296
+ "operationId": "authLogout"
297
+ },
298
+ {
299
+ "method": "GET",
300
+ "pattern": "/auth/logout",
301
+ "operationId": "authLogoutLink"
302
+ },
303
+ {
304
+ "method": "GET",
305
+ "pattern": "/v1/admin/stats",
306
+ "operationId": "adminStats"
307
+ },
308
+ {
309
+ "method": "GET",
310
+ "pattern": "/v1/admin/keys",
311
+ "operationId": "adminListKeys"
312
+ },
313
+ {
314
+ "method": "POST",
315
+ "pattern": "/v1/admin/keys",
316
+ "operationId": "adminCreateKey"
317
+ }
318
+ ] as const