gemi 0.140.1 → 0.141.0
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.
- package/dist/chunk-03qkwjvy.js +24 -0
- package/dist/{chunk-2vfdahm9.js.map → chunk-03qkwjvy.js.map} +3 -3
- package/dist/{chunk-1vfnehjr.js → chunk-0540ptgm.js} +1 -1
- package/dist/{chunk-bmam8bsm.js → chunk-1axhttzr.js} +1 -1
- package/dist/{chunk-bywhpfyv.js → chunk-akahs8fn.js} +5 -5
- package/dist/{chunk-bywhpfyv.js.map → chunk-akahs8fn.js.map} +4 -4
- package/dist/{chunk-679ndcdq.js → chunk-b97tpvet.js} +2 -2
- package/dist/{chunk-679ndcdq.js.map → chunk-b97tpvet.js.map} +1 -1
- package/dist/chunks/{RealtimeClient-DFgd6k2O.js → RealtimeClient-D8AB_ksF.js} +2 -2
- package/dist/chunks/{RealtimeClient-DFgd6k2O.js.map → RealtimeClient-D8AB_ksF.js.map} +1 -1
- package/dist/chunks/{ThemeProvider-D8T12nBG.js → ThemeProvider-BV3tyt_8.js} +4 -4
- package/dist/chunks/{ThemeProvider-D8T12nBG.js.map → ThemeProvider-BV3tyt_8.js.map} +1 -1
- package/dist/chunks/{fetchFileRequest-Bu8thkJP.js → fetchFileRequest-B2rjj_hy.js} +2 -2
- package/dist/chunks/{fetchFileRequest-Bu8thkJP.js.map → fetchFileRequest-B2rjj_hy.js.map} +1 -1
- package/dist/chunks/{live-CR8v1KcI.js → live-COU4GpXn.js} +3 -3
- package/dist/chunks/{live-CR8v1KcI.js.map → live-COU4GpXn.js.map} +1 -1
- package/dist/chunks/{mutationExtras-DuFelh7K.js → mutationExtras-idr-Ane0.js} +2 -2
- package/dist/chunks/{mutationExtras-DuFelh7K.js.map → mutationExtras-idr-Ane0.js.map} +1 -1
- package/dist/client/ClientRouter.d.ts.map +1 -1
- package/dist/client/ClientRouterContext.d.ts.map +1 -1
- package/dist/client/index.js +6 -5
- package/dist/client/index.js.map +1 -1
- package/dist/facades/index.js +1 -1
- package/dist/http/index.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/kernel/index.js +1 -1
- package/dist/server/httpProd.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/router/ViewRouteDispatcher.d.ts.map +1 -1
- package/dist/services/router/config.d.ts +12 -0
- package/dist/services/router/config.d.ts.map +1 -1
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2vfdahm9.js +0 -24
- /package/dist/{chunk-1vfnehjr.js.map → chunk-0540ptgm.js.map} +0 -0
- /package/dist/{chunk-bmam8bsm.js.map → chunk-1axhttzr.js.map} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{S as w,T as zJ,U as KJ}from"./chunk-b97tpvet.js";import{oa as M,sa as I,ta as OJ}from"./chunk-a22h5cby.js";import"./chunk-kk18xqgc.js";import{ae as t,ng as HJ,og as B}from"./chunk-akahs8fn.js";import"./chunk-9b4ypfcw.js";import"./chunk-5013raxj.js";import{Ah as i,Bh as a,Eh as r}from"./chunk-eve0hewa.js";import{Gi as GJ}from"./chunk-v2cv3wcw.js";import"./chunk-7hdfbp2x.js";import"./chunk-dzvp8ynr.js";import"./chunk-23mcs80p.js";import"./chunk-e9xbdgb2.js";import"./chunk-gy307m19.js";import"./chunk-98dxtkbt.js";import"./chunk-ks7msv3e.js";import"./chunk-yxjfghq3.js";import"./chunk-8r9kna46.js";import"./chunk-rwrcsfzv.js";import"./chunk-rw4z3n9b.js";import"./chunk-kddgfct7.js";import"./chunk-s58hbczk.js";import{Yk as $J}from"./chunk-apfk702c.js";import"./chunk-eyqsvzej.js";import{kl as UJ}from"./chunk-9yfg4nqq.js";import{ll as R}from"./chunk-5aj27bgc.js";import"./chunk-tbhtx2t0.js";import{createHash as H2}from"crypto";import{join as v,resolve as xJ,sep as LJ}from"path";import{Duplex as bJ}from"stream";import{constants as N,createBrotliCompress as SJ,createGzip as kJ}from"zlib";var wJ=["br","gzip"],vJ=1024,EJ=5,gJ=19,yJ=6;function fJ(J){if(J===void 0)return 1;if(!/^\d+(\.\d{0,3})?$/.test(J.trim()))return 1;let $=Number(J.trim());return Number.isFinite($)?Math.min($,1):1}function mJ(J){if(J===null||J===void 0)return null;let $=new Map;for(let Z of J.split(",")){let[X,...Q]=Z.split(";"),H=X.trim().toLowerCase();if(H==="")continue;let j=1;for(let Y of Q){let V=Y.indexOf("=");if(V===-1)continue;if(Y.slice(0,V).trim().toLowerCase()==="q"){j=fJ(Y.slice(V+1));break}}$.set(H,j)}return $}function b(J,$=wJ){let Z=mJ(J);if(!Z)return null;let X=Z.get("*"),Q=null,H=0;for(let j of $){let Y=Z.get(j)??X??0;if(Y>H)Q=j,H=Y}return Q}function uJ(J,$){if(!J)return!1;return J.split(",").some((Z)=>Z.trim().toLowerCase().split("=")[0].trim()===$)}function hJ(J,$){if(J==="HEAD")return!1;if($.status<200||$.status===204||$.status===206||$.status===304)return!1;if($.headers.has("Content-Range"))return!1;let Z=$.headers.get("Content-Type");if(!Z||!/^\s*text\/html\s*(;|$)/i.test(Z))return!1;if(!$.body)return!1;let X=$.headers.get("Content-Encoding");if(X&&X.trim().toLowerCase()!=="identity")return!1;if(uJ($.headers.get("Cache-Control"),"no-transform"))return!1;let Q=$.headers.get("Content-Length");if(Q!==null&&Number(Q)<vJ)return!1;return!0}function cJ(J){let $=J==="br"?SJ({params:{[N.BROTLI_PARAM_QUALITY]:EJ,[N.BROTLI_PARAM_LGWIN]:gJ},flush:N.BROTLI_OPERATION_FLUSH}):kJ({level:yJ,flush:N.Z_SYNC_FLUSH});return bJ.toWeb($)}function pJ(J){let $=J.get("Vary");if(!$){J.set("Vary","Accept-Encoding");return}let Z=$.split(",").map((X)=>X.trim().toLowerCase());if(Z.includes("*")||Z.includes("accept-encoding"))return;J.append("Vary","Accept-Encoding")}function d(J,$){if(!hJ(J.method,$))return $;let Z=new Headers($.headers);pJ(Z);let X=b(J.headers.get("Accept-Encoding"));if(!X)return new Response($.body,{status:$.status,statusText:$.statusText,headers:Z});let Q;try{Q=cJ(X)}catch{return new Response($.body,{status:$.status,statusText:$.statusText,headers:Z})}return Z.set("Content-Encoding",X),Z.delete("Content-Length"),new Response($.body.pipeThrough(Q),{status:$.status,statusText:$.statusText,headers:Z})}import{readdir as rJ,stat as s}from"fs/promises";import{join as tJ}from"path";import{mkdir as x2,readFile as dJ,stat as L2,writeFile as C2}from"fs/promises";import{dirname as F2,join as lJ}from"path";import{promisify as l}from"util";import{brotliCompress as oJ,constants as P2,gzip as nJ}from"zlib";var iJ=".vite/static-assets.json";var o={br:".br",gzip:".gz"},aJ=["br","gzip"],N2=l(oJ),A2=l(nJ);async function n(J){let $;try{$=await dJ(lJ(J,iJ),"utf8")}catch{return null}let Z;try{Z=JSON.parse($)?.files}catch{return null}if(!Z||typeof Z!=="object")return null;let X={files:{}};for(let[Q,H]of Object.entries(Z)){if(!Array.isArray(H))continue;X.files[Q]=aJ.filter((j)=>H.includes(j))}return X}function S(J){let $=J.toString(),Z=new Bun.CryptoHasher("sha1");return Z.update($),Z.digest("hex")}async function e(J){let $=new Set;async function Z(X,Q){let H;try{H=await rJ(X,{withFileTypes:!0})}catch{return}for(let j of H){if(j.name.startsWith("."))continue;if(Q&&j.name==="assets")continue;let Y=tJ(X,j.name),V=j.isDirectory(),U=j.isFile();if(j.isSymbolicLink())try{let x=await s(Y);V=x.isDirectory(),U=x.isFile()}catch{continue}if(U)$.add(Y);else if(V&&!j.isSymbolicLink())await Z(Y,!1)}}return await Z(J,!0),$}var sJ="public, max-age=31536000, must-revalidate",eJ="public, max-age=31536000, immutable";async function J2(J,$,Z){if(Z.length===0||J.headers.has("Range"))return null;let X=b(J.headers.get("Accept-Encoding"),Z);if(!X)return null;let Q=$+o[X];try{let H=await s(Q);return H.isFile()?{encoding:X,path:Q,size:H.size}:null}catch{return null}}async function JJ(J,$,Z={}){let X=Bun.file($),Q=Z.encodings??[],H={"Content-Type":X.type||"application/octet-stream","Cache-Control":Z.immutable?eJ:sJ,"Accept-Ranges":"bytes"};if(Q.length>0)H.Vary="Accept-Encoding";let j=await J2(J,$,Q);if(j){let U=Bun.file(j.path);return new Response(U.stream(),{headers:{...H,"Content-Encoding":j.encoding,"Content-Length":String(j.size),ETag:`${S(U.lastModified)}-${j.encoding}`}})}let Y=X.size;H.ETag=S(X.lastModified);let V=i(J.headers.get("Range"));if(V){let U=a(V,Y);if(!U)return t(Y);return new Response(X.slice(U.start,U.end+1).stream(),{status:206,headers:{...H,"Content-Range":r(U.start,U.end,Y),"Content-Length":String(U.length)}})}return new Response(X.stream(),{headers:{...H,"Content-Length":String(Y)}})}import{stat as j2}from"fs/promises";var A="app/client.tsx";function ZJ(J,$=M){let Z=J[A]?.file;if(!Z)return;return{module:I(Z,$),preload:C(J,A,$)}}function C(J,$,Z=M){let X=[],Q=new Set,H=(j)=>{if(Q.has(j))return;Q.add(j);let Y=J[j];if(!Y?.file)return;X.push(I(Y.file,Z));for(let V of Y.imports??[])H(V)};return H($),X}function $2(J,$,Z=M){let X=new Set(C(J,$,Z)),Q=[],H=new Set,j=(V)=>{if(H.has(V))return;H.add(V);let U=J[V];if(!U)return;for(let x of U.dynamicImports??[])if(!/(^|\/)node_modules\//.test(x)&&!Q.includes(x))Q.push(x);for(let x of U.imports??[])j(x)};j($);let Y=[];for(let V of Q)for(let U of C(J,V,Z))if(!X.has(U))X.add(U),Y.push(U);return Y}function XJ(J,$){let Z=[],X=new Set,Q=new Set,H=(j)=>{if(Q.has(j))return;Q.add(j);let Y=J[j];if(!Y)return;for(let V of Y.imports??[])H(V);for(let V of Y.css??[])if(!X.has(V))X.add(V),Z.push(V)};return H($),Z}function QJ(J,$=M){let Z=new Map;return(X)=>{if(!Z.has(X)){let Q=`${X}${$J}`,H=J[Q]?.file;Z.set(X,H?{src:I(H,$),preload:C(J,Q,$),dynamicPreload:$2(J,Q,$)}:void 0)}return Z.get(X)}}function WJ(J,$=30000){let Z=JSON.stringify("gemi:chunk-reload"),X=JSON.stringify(`Failed to fetch dynamically imported module: ${J}`);return`var r=!1;try{var s=window.sessionStorage,m=JSON.parse(s.getItem(${Z})||"null");var d=m?Date.now()-m.at:NaN;if(navigator.onLine!==!1&&!(d>=0&&d<${$})){s.setItem(${Z},JSON.stringify({url:window.location.href,at:Date.now()}));r=!0}}catch(e){}if(r)window.location.reload();else throw new Error(${X});export {}`}var Z2=new RegExp(`^${HJ}/.+\\.m?js$`);function X2(J){return Z2.test(J)}function jJ(J){if(X2(J))return new Response(WJ(J),{headers:{"Content-Type":"application/javascript","Cache-Control":"no-store"}});if(B(J))return new Response("Not found",{status:404});return}var Q2=`<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Internal Server Error</title>
|
|
8
|
+
<style>
|
|
9
|
+
body { font-family: system-ui, sans-serif; margin: 0; min-height: 100vh; display: grid; place-items: center; }
|
|
10
|
+
main { text-align: center; padding: 16px; }
|
|
11
|
+
h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
|
|
12
|
+
p { margin: 0; opacity: 0.7; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<main>
|
|
17
|
+
<h1>Something went wrong</h1>
|
|
18
|
+
<p>Please try again later.</p>
|
|
19
|
+
</main>
|
|
20
|
+
</body>
|
|
21
|
+
</html>`;function k(J,$,Z){if(!Z)console.error(J);else try{Z(J instanceof Error?J:Error(String(J),{cause:J}))}catch(X){console.error(J),console.error(X)}if(R($))return new Response(JSON.stringify({error:GJ("server_error","Internal Server Error",500)}),{status:500,headers:{"Content-Type":"application/json"}});return new Response(Q2,{status:500,headers:{"Content-Type":"text/html; charset=utf-8"}})}var W2={kind:"none"};function YJ(J){let $=(J??"").trim().toLowerCase();if($===""||$==="false"||$==="off"||$==="0")return W2;if($==="true")return{kind:"all"};if(/^[1-9]\d*$/.test($))return{kind:"hops",hops:Number($)};throw Error(`GEMI_TRUST_PROXY must be "false", "true" or the number of proxies in front of the app (e.g. 1). Got ${JSON.stringify(J)}.`)}function VJ(J,$,Z){if(Z.kind==="all"){if(!J.get("x-forwarded-for")&&$)J.set("x-forwarded-for",$);return}J.delete("x-real-ip");let X=$;if(Z.kind==="hops"){let Q=(J.get("x-forwarded-for")??"").split(",").map((j)=>j.trim()).filter(Boolean),H=Q.length-Z.hops;X=H>=0?Q[H]:Q[0]??$}if(X)J.set("x-forwarded-for",X);else J.delete("x-forwarded-for")}var T=UJ(),G2=v(T,"app"),L=v(T,"dist"),F=v(L,"client");function Y2(J){let $;try{$=decodeURIComponent(J)}catch{return null}let Z=xJ(F,"."+$.replace("/assets/assets","/assets"));return Z.startsWith(F+LJ)?Z:null}async function V2(J){try{return(await j2(J)).isFile()}catch{return!1}}async function G3(J,$){let Z=await import(`${L}/client/.vite/manifest.json`),X=await import(`${L}/server/.vite/manifest.json`),Q=await OJ(`${L}/client`);process.env.ROOT_DIR=T,process.env.APP_DIR=G2,process.env.DIST_DIR=L;let H={},j={},Y={},V={},U={},x=(W,G)=>`"${W}": () => import("${G}")`,E=[];for(let W of["404",...J.getFlatComponentTree.call(J)]){let G=X[`app/views/${W}.tsx`];if(!G?.file){console.log(`Server file not found for ${W}`),console.log(G);let K=Object.keys(X),P=`app/views/${W}.tsx`;console.log(`${P} not found in server manifest`),console.log(K)}let O=await import(`${process.env.DIST_DIR}/server/${G?.file}`);H[W]=O.default,j[W]=O.OpenGraph,Y[W]=O;let z=Z[`app/views/${W}.tsx`];if(z?.css&&z?.css.length>0)V[W]=z?.css;if(z)E.push(x(W,I(z?.file,Q))),U[W]=C(Z,`app/views/${W}.tsx`,Q)}let CJ=`{${E.join(",")}}`,g=ZJ(Z,Q);if(!g)console.error(`Client manifest has no "${A}" entry \u2014 dist/client is incomplete, so documents will render but never hydrate.`);let IJ=Z["app/client.tsx"]?.css??[],y=(await Promise.all(IJ.map((W)=>Bun.file(`${L}/client/${W}`).text()))).join(`
|
|
22
|
+
`),FJ=QJ(Z,Q),_J=H2("sha256").update(await Bun.file(`${L}/client/.vite/manifest.json`).text()).digest("base64url").slice(0,12),f=new Map,MJ=(W)=>{let G=f.get(W);if(!G)G=Bun.file(`${L}/client/${W}`).text(),f.set(W,G);return G},PJ=async(W,G)=>{let O=new Set;for(let K of G?[G,...W]:W)for(let P of XJ(Z,`app/views/${K}.tsx`))O.add(P);let z=await Promise.all([...O].map(async(K)=>({id:K,content:await MJ(K)})));return w(G?z:[{content:y},...z])},NJ=await e(F),m=new Map;for(let[W,G]of Object.entries((await n(F))?.files??{})){let O=xJ(F,W);if(O.startsWith(F+LJ))m.set(O,{immutable:!0,encodings:G})}async function AJ(W){let{pathname:G}=new URL(W.url);if(R(G))return await D(W,G);let O=Y2(G);if(B(G)||G.startsWith("/.well-known")||O!==null&&NJ.has(O)){if(!O||!await V2(O))return jJ(G)??await D(W,G);try{return await JJ(W,O,m.get(O))}catch(K){return J.onException?.(K),new Response("Not found",{status:404})}}return await D(W,G)}async function D(W,G){let O=J.fetch.bind(J);try{let z=await O(W);if(z instanceof Response)return z;else{let K=[];return K.push({content:y}),await z({getStyles:async(RJ,h)=>{let c=RJ??["404"];if(h?.static)return PJ(c,h.layout);for(let TJ of c){let DJ=Z[`app/views/${TJ}.tsx`];for(let p of DJ?.css??[]){let qJ=Bun.file(`${process.env.DIST_DIR}/client/${p}`);K.push({id:p,content:await qJ.text()})}}return w(K)},clientEntry:g,modulePreloadManifest:U,loaders:CJ,viewImportMap:H,viewModules:Y,ogMap:j,cssManifest:V,assetBase:Q,resolveIsland:FJ,buildId:_J})}}catch(z){return k(z,G,J.onException)}}let BJ=(process.env.GEMI_COMPRESSION??"auto").toLowerCase()!=="off",u=YJ(process.env.GEMI_TRUST_PROXY),_=J.sockets();if(_&&u.kind==="none")console.warn("[gemi] Broadcast sockets: GEMI_TRUST_PROXY is not set, so a socket's address is its TCP peer. Behind a proxy or load balancer every client shares the proxy's address, and broadcast.maxConnectionsPerIp caps the whole app per process. Set GEMI_TRUST_PROXY (see forwardedFor) or raise that limit.");let q=Bun.serve({maxRequestBodySize:10737418240,fetch:async(W,G)=>{if(VJ(W.headers,G.requestIP(W)?.address??null,u),J.applyTrustedClientIp(W),_?.matches(W))return _.upgrade(W,G);let O=await $(W,(z)=>J.withGlobalMiddleware(z,AJ,(K)=>k(K,new URL(z.url).pathname,J.onException)));return BJ?d(W,O):O},idleTimeout:KJ(),port:process.env.PORT||5173,websocket:_?.websocket});return await _?.start(q),zJ({port:q.port,rootDir:T}),q}export{G3 as httpProd};
|
|
23
|
+
|
|
24
|
+
//# debugId=AF73DF72B0CE2E5964756E2164756E21
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../server/httpProd.ts", "../server/compression.ts", "../server/staticFile.ts", "../vite/precompressAssets.ts", "../server/generateEtag.ts", "../server/modulePreloads.ts", "../client/chunkLoadRecovery.ts", "../server/staticAssetMiss.ts", "../server/unhandledError.ts", "../server/forwardedFor.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { createHash } from \"node:crypto\";\nimport type { WebSocketHandler } from \"bun\";\nimport { join, resolve, sep } from \"node:path\";\nimport { compressResponse } from \"./compression\";\nimport { listPublicFiles, staticFileResponse } from \"./staticFile\";\nimport { stat } from \"node:fs/promises\";\nimport { createStyles } from \"./styles\";\nimport {\n CLIENT_ENTRY_KEY,\n collectCss,\n collectModulePreloads,\n createClientEntry,\n createIslandResolver,\n} from \"./modulePreloads\";\nimport type { App } from \"../app\";\nimport { Instrumentation } from \"./types\";\nimport { printStartupBanner } from \"./banner\";\nimport { isReservedAssetPath, staticAssetMiss } from \"./staticAssetMiss\";\nimport { assetUrl, readBuiltAssetBase } from \"../config/assetBase\";\nimport { isApiPath } from \"../services/router/apiPath\";\nimport { projectRoot } from \"../support/discover\";\nimport { unhandledErrorResponse } from \"./unhandledError\";\nimport { serverIdleTimeout } from \"./idleTimeout\";\nimport { applyForwardedTrust, parseTrustProxy } from \"./forwardedFor\";\nimport { readStaticAssetsRecord } from \"../vite/precompressAssets\";\nimport type { StaticFileOptions } from \"./staticFile\";\n\n// The rule this file used to spell out itself. It moved to `projectRoot`\n// because discovery needs the same answer during `waitForBoot()`, which is\n// before `ROOT_DIR` below exists — and two spellings of \"where is the project\"\n// is how a job gets looked for in one directory and served from another.\nconst rootDir = projectRoot();\n\nconst appDir = join(rootDir, \"app\");\nconst distDir = join(rootDir, \"dist\");\n\nconst clientDir = join(distDir, \"client\");\n\n// The file in `dist/client` a static-looking pathname names, or `null` when\n// it names nothing there. The pathname is still percent-encoded, and Vite\n// writes files whose names need encoding (a public `my font.woff2`), so it is\n// decoded first. Decoding also turns `%2F` into a separator, which the URL\n// parser's own `..` normalization never saw — so the resolved path is checked\n// to still be inside `dist/client`. A malformed escape is a miss, not a 500.\nfunction clientFilePath(pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const path = resolve(clientDir, \".\" + decoded.replace(\"/assets/assets\", \"/assets\"));\n return path.startsWith(clientDir + sep) ? path : null;\n}\n\nasync function isFile(path: string) {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\n// `app` is built by `Server.start` (from the kernel) and passed in — same as\n// `httpDev` — so prod and dev share one construction path. What differs here is\n// that views/styles are served from the built `dist/` manifests instead of\n// Vite's dev SSR graph.\nexport async function httpProd(app: App, instrumentation: Instrumentation) {\n const manifest = await import(`${distDir}/client/.vite/manifest.json`);\n const serverManifest = await import(`${distDir}/server/.vite/manifest.json`);\n // What the client build was built with, not `GEMI_ASSET_BASE` as it reads\n // now — see `readBuiltAssetBase`. Every URL below that names a chunk goes\n // through it, so the document links exactly what the bundle imports.\n const assetBase = await readBuiltAssetBase(`${distDir}/client`);\n\n process.env.ROOT_DIR = rootDir;\n process.env.APP_DIR = appDir;\n process.env.DIST_DIR = distDir;\n\n const viewImportMap = {};\n const ogMap = {};\n const viewModules = {};\n const cssManifest = {};\n // Which chunks a route's views pull in, so the shell can preload the chain\n // the client would otherwise discover one round trip at a time (#352).\n const modulePreloadManifest: Record<string, string[]> = {};\n const template = (viewName: string, path: string) => `\"${viewName}\": () => import(\"${path}\")`;\n const templates = [];\n\n for (const fileName of [\"404\", ...app.getFlatComponentTree.call(app)]) {\n const serverFile = serverManifest[`app/views/${fileName}.tsx`];\n if (!serverFile?.file) {\n console.log(`Server file not found for ${fileName}`);\n console.log(serverFile);\n const files = Object.keys(serverManifest);\n const path = `app/views/${fileName}.tsx`;\n console.log(`${path} not found in server manifest`);\n console.log(files);\n }\n const mod = await import(`${process.env.DIST_DIR}/server/${serverFile?.file}`);\n viewImportMap[fileName] = mod.default;\n ogMap[fileName] = mod.OpenGraph;\n // The whole module, so a streaming render can put the view's\n // `Loading`/`Error` exports into the shell it sends.\n viewModules[fileName] = mod;\n const clientFile = manifest[`app/views/${fileName}.tsx`];\n\n if (clientFile?.css && clientFile?.css.length > 0) {\n cssManifest[fileName] = clientFile?.css;\n }\n if (clientFile) {\n templates.push(template(fileName, assetUrl(clientFile?.file, assetBase)));\n modulePreloadManifest[fileName] = collectModulePreloads(\n manifest,\n `app/views/${fileName}.tsx`,\n assetBase,\n );\n }\n }\n\n const loaders = `{${templates.join(\",\")}}`;\n\n // Booted from the bootstrap script rather than through React's\n // `bootstrapModules`, which would preload it at `fetchPriority=\"low\"` — see\n // `clientEntry` in `ViewRouteDispatcher`.\n const clientEntry = createClientEntry(manifest, assetBase);\n if (!clientEntry) {\n // Loudly, but without dying: this is boot, and an incomplete `dist/` must\n // not cost the API routes and static assets too.\n console.error(\n `Client manifest has no \"${CLIENT_ENTRY_KEY}\" entry — dist/client is incomplete, so documents will render but never hydrate.`,\n );\n }\n\n // Vite's manifest `css` field is a `string[]` — a client entry can emit more\n // than one CSS chunk. Read and concatenate them all instead of interpolating\n // the array into a single path (which only works for a one-element array).\n const appCssFiles: string[] = manifest[\"app/client.tsx\"]?.css ?? [];\n const appCSSContent = (\n await Promise.all(appCssFiles.map((cssFile) => Bun.file(`${distDir}/client/${cssFile}`).text()))\n ).join(\"\\n\");\n\n // The island entries (`<module>?gemi-island`) a static view's document\n // loads; see `injectIslands`.\n const resolveIsland = createIslandResolver(manifest, assetBase);\n\n // Identifies this client build: a static page with `navigation` swaps in\n // only pages of the same build. The manifest names every content-hashed\n // file, so it changes whenever any of them does.\n const buildId = createHash(\"sha256\")\n .update(await Bun.file(`${distDir}/client/.vite/manifest.json`).text())\n .digest(\"base64url\")\n .slice(0, 12);\n\n // A static view's CSS comes from the whole import closure of its views (and\n // its own layout's, which then replaces the app stylesheet): there is no\n // client to fetch a shared chunk's CSS later, as a hydrated page's chunk\n // loader does. Read once per file: the build does not change under a\n // running server.\n const cssFileCache = new Map<string, Promise<string>>();\n const readCss = (file: string) => {\n let text = cssFileCache.get(file);\n if (!text) {\n text = Bun.file(`${distDir}/client/${file}`).text();\n cssFileCache.set(file, text);\n }\n return text;\n };\n const staticStyles = async (views: string[], layout: string | undefined) => {\n const files = new Set<string>();\n for (const view of layout ? [layout, ...views] : views) {\n for (const file of collectCss(manifest, `app/views/${view}.tsx`)) files.add(file);\n }\n const styles = await Promise.all(\n [...files].map(async (file) => ({ id: file, content: await readCss(file) })),\n );\n return createStyles(layout ? styles : [{ content: appCSSContent }, ...styles]);\n };\n\n // Which requests are answered from `dist/client` rather than the app:\n //\n // - `/assets` and everything under it, whatever the extension. Vite writes\n // fonts, GIFs, video and `?url` JSON there too, and an allowlist of\n // extensions always trailed the build — a `.woff2` went to the router\n // and paid for an SSR attempt. The router refuses routes under `/assets`\n // at boot, so there is no app answer to lose, and `staticAssetMiss`\n // turns a miss into the reload stub or a 404.\n // - `/.well-known/*`.\n // - Any other path that names a file the build copied out of `public/`,\n // whatever its extension (#583). Any other path may be an app route, so\n // it is static only when that exact file exists — read once here, since\n // the build output does not change under a running server. A view at\n // `/manifest` keeps its data URL (`/manifest.json`) unless the app also\n // ships `public/manifest.json`, and then the file wins. View data is\n // never a file in `dist/client`, so a client-side navigation pays for no\n // filesystem lookup.\n const publicFiles = await listPublicFiles(clientDir);\n\n // The build assets `gemi build` emitted or carried over, with the\n // precompressed siblings it wrote for each (#789). Read once: the build\n // output does not change under a running server. Without the record (a\n // build made before it existed) every file is served as identity with the\n // old cache policy.\n const staticAssets = new Map<string, StaticFileOptions>();\n for (const [file, encodings] of Object.entries(\n (await readStaticAssetsRecord(clientDir))?.files ?? {},\n )) {\n const path = resolve(clientDir, file);\n if (path.startsWith(clientDir + sep)) {\n staticAssets.set(path, { immutable: true, encodings });\n }\n }\n\n async function requestHandler(req: Request) {\n const { pathname } = new URL(req.url);\n\n if (isApiPath(pathname)) {\n return await handleWithApp(req, pathname);\n }\n\n const distPath = clientFilePath(pathname);\n const isFileRequest =\n isReservedAssetPath(pathname) ||\n pathname.startsWith(\"/.well-known\") ||\n (distPath !== null && publicFiles.has(distPath));\n\n if (isFileRequest) {\n // Served from here whatever the asset base is: a CDN in front of the\n // app uses this origin as the source it fills from. A file, not merely\n // a path that exists: `/assets` itself is `dist/client/assets`, a\n // directory, and streaming one would answer 200 and then fail mid-body.\n //\n // `Bun.file(path).stream()` is lazy — a missing file only throws ENOENT\n // once the body is streamed, which is *after* this handler has returned,\n // with the response already committed as 200. So existence is checked\n // here, per request, even for a path `publicFiles` lists.\n if (!distPath || !(await isFile(distPath))) {\n return staticAssetMiss(pathname) ?? (await handleWithApp(req, pathname));\n }\n\n try {\n return await staticFileResponse(req, distPath, staticAssets.get(distPath));\n } catch (error) {\n app.onException?.(error);\n return new Response(\"Not found\", { status: 404 });\n }\n }\n\n return await handleWithApp(req, pathname);\n }\n\n async function handleWithApp(req: Request, pathname: string) {\n const handler = app.fetch.bind(app);\n\n try {\n const result = await handler(req);\n if (result instanceof Response) {\n return result;\n } else {\n const styles = [];\n\n styles.push({\n content: appCSSContent,\n });\n\n const getStyles = async (\n currentViews: string[],\n options?: { static?: boolean; layout?: string },\n ) => {\n if (!currentViews) {\n return createStyles([]);\n }\n if (options?.static) {\n return staticStyles(currentViews, options.layout);\n }\n for (const view of currentViews) {\n const clientFile = manifest[`app/views/${view}.tsx`];\n for (const cssFile of clientFile?.css ?? []) {\n const css = Bun.file(`${process.env.DIST_DIR}/client/${cssFile}`);\n styles.push({\n id: cssFile,\n content: await css.text(),\n });\n }\n }\n return createStyles(styles);\n };\n\n return await result({\n getStyles,\n clientEntry,\n modulePreloadManifest,\n loaders,\n viewImportMap,\n viewModules,\n ogMap,\n cssManifest,\n assetBase,\n resolveIsland,\n buildId,\n });\n }\n } catch (err) {\n return unhandledErrorResponse(err, pathname, app.onException);\n }\n }\n\n // Compression is on unless the deployment opts out — set `GEMI_COMPRESSION=off`\n // when a layer in front of the origin already compresses HTML and you would\n // rather not spend origin CPU on it.\n const compressionEnabled = (process.env.GEMI_COMPRESSION ?? \"auto\").toLowerCase() !== \"off\";\n\n // Which `X-Forwarded-For` entries to believe — see `forwardedFor.ts`. Parsed\n // before `Bun.serve` so a bad value fails the boot, not every request.\n const forwardedTrust = parseTrustProxy(process.env.GEMI_TRUST_PROXY);\n\n // The broadcast socket endpoint, when the app declares `route.channels`.\n const sockets = app.sockets();\n if (sockets && forwardedTrust.kind === \"none\") {\n console.warn(\n \"[gemi] Broadcast sockets: GEMI_TRUST_PROXY is not set, so a socket's address is its TCP peer. Behind a proxy or load balancer every client shares the proxy's address, and broadcast.maxConnectionsPerIp caps the whole app per process. Set GEMI_TRUST_PROXY (see forwardedFor) or raise that limit.\",\n );\n }\n\n const server = Bun.serve<any>({\n maxRequestBodySize: 10 * 1024 * 1024 * 1024, // 10 GB\n fetch: async (req, server) => {\n // `requestIP` is null for closed/unix sockets — guard so it never\n // throws before the request is handled.\n applyForwardedTrust(req.headers, server.requestIP(req)?.address ?? null, forwardedTrust);\n // A proxy that proves itself with `route.domains.trustProxy.secret` may\n // name the client in `clientIpHeader`, which then wins.\n app.applyTrustedClientIp(req);\n // Ahead of the instrumentation: an upgrade is not a response to trace,\n // and the hub runs the global middleware itself.\n if (sockets?.matches(req)) {\n return sockets.upgrade(req, server);\n }\n // The app's global middleware goes in front of the static handler as well\n // as the router, so it can refuse `/assets/*` too.\n const res = await instrumentation(req, (req) =>\n app.withGlobalMiddleware(req, requestHandler, (err) =>\n unhandledErrorResponse(err, new URL(req.url).pathname, app.onException),\n ),\n );\n // Applied at the very edge, after instrumentation, so every HTML response\n // goes through the same negotiation — including the ones an app's\n // instrumentation produced itself. Anything that isn't HTML comes back\n // untouched.\n return compressionEnabled ? compressResponse(req, res) : res;\n },\n idleTimeout: serverIdleTimeout(),\n port: process.env.PORT || 5173,\n websocket: sockets?.websocket as WebSocketHandler<any>,\n });\n await sockets?.start(server);\n\n printStartupBanner({ port: server.port, rootDir });\n\n return server;\n}\n",
|
|
5
|
+
"import { createHash } from \"node:crypto\";\nimport type { WebSocketHandler } from \"bun\";\nimport { join, resolve, sep } from \"node:path\";\nimport { compressResponse } from \"./compression\";\nimport { listPublicFiles, staticFileResponse } from \"./staticFile\";\nimport { stat } from \"node:fs/promises\";\nimport { createStyles } from \"./styles\";\nimport {\n CLIENT_ENTRY_KEY,\n collectCss,\n collectModulePreloads,\n createClientEntry,\n createIslandResolver,\n} from \"./modulePreloads\";\nimport type { App } from \"../app\";\nimport { Instrumentation } from \"./types\";\nimport { printStartupBanner } from \"./banner\";\nimport { isReservedAssetPath, staticAssetMiss } from \"./staticAssetMiss\";\nimport { assetUrl, readBuiltAssetBase } from \"../config/assetBase\";\nimport { isApiPath } from \"../services/router/apiPath\";\nimport { projectRoot } from \"../support/discover\";\nimport { unhandledErrorResponse } from \"./unhandledError\";\nimport { serverIdleTimeout } from \"./idleTimeout\";\nimport { applyForwardedTrust, parseTrustProxy } from \"./forwardedFor\";\nimport { readStaticAssetsRecord } from \"../vite/precompressAssets\";\nimport type { StaticFileOptions } from \"./staticFile\";\n\n// The rule this file used to spell out itself. It moved to `projectRoot`\n// because discovery needs the same answer during `waitForBoot()`, which is\n// before `ROOT_DIR` below exists — and two spellings of \"where is the project\"\n// is how a job gets looked for in one directory and served from another.\nconst rootDir = projectRoot();\n\nconst appDir = join(rootDir, \"app\");\nconst distDir = join(rootDir, \"dist\");\n\nconst clientDir = join(distDir, \"client\");\n\n// The file in `dist/client` a static-looking pathname names, or `null` when\n// it names nothing there. The pathname is still percent-encoded, and Vite\n// writes files whose names need encoding (a public `my font.woff2`), so it is\n// decoded first. Decoding also turns `%2F` into a separator, which the URL\n// parser's own `..` normalization never saw — so the resolved path is checked\n// to still be inside `dist/client`. A malformed escape is a miss, not a 500.\nfunction clientFilePath(pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const path = resolve(clientDir, \".\" + decoded.replace(\"/assets/assets\", \"/assets\"));\n return path.startsWith(clientDir + sep) ? path : null;\n}\n\nasync function isFile(path: string) {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\n// `app` is built by `Server.start` (from the kernel) and passed in — same as\n// `httpDev` — so prod and dev share one construction path. What differs here is\n// that views/styles are served from the built `dist/` manifests instead of\n// Vite's dev SSR graph.\nexport async function httpProd(app: App, instrumentation: Instrumentation) {\n const manifest = await import(`${distDir}/client/.vite/manifest.json`);\n const serverManifest = await import(`${distDir}/server/.vite/manifest.json`);\n // What the client build was built with, not `GEMI_ASSET_BASE` as it reads\n // now — see `readBuiltAssetBase`. Every URL below that names a chunk goes\n // through it, so the document links exactly what the bundle imports.\n const assetBase = await readBuiltAssetBase(`${distDir}/client`);\n\n process.env.ROOT_DIR = rootDir;\n process.env.APP_DIR = appDir;\n process.env.DIST_DIR = distDir;\n\n const viewImportMap = {};\n const ogMap = {};\n const viewModules = {};\n const cssManifest = {};\n // Which chunks a route's views pull in, so the shell can preload the chain\n // the client would otherwise discover one round trip at a time (#352).\n const modulePreloadManifest: Record<string, string[]> = {};\n const template = (viewName: string, path: string) => `\"${viewName}\": () => import(\"${path}\")`;\n const templates = [];\n\n for (const fileName of [\"404\", ...app.getFlatComponentTree.call(app)]) {\n const serverFile = serverManifest[`app/views/${fileName}.tsx`];\n if (!serverFile?.file) {\n console.log(`Server file not found for ${fileName}`);\n console.log(serverFile);\n const files = Object.keys(serverManifest);\n const path = `app/views/${fileName}.tsx`;\n console.log(`${path} not found in server manifest`);\n console.log(files);\n }\n const mod = await import(`${process.env.DIST_DIR}/server/${serverFile?.file}`);\n viewImportMap[fileName] = mod.default;\n ogMap[fileName] = mod.OpenGraph;\n // The whole module, so a streaming render can put the view's\n // `Loading`/`Error` exports into the shell it sends.\n viewModules[fileName] = mod;\n const clientFile = manifest[`app/views/${fileName}.tsx`];\n\n if (clientFile?.css && clientFile?.css.length > 0) {\n cssManifest[fileName] = clientFile?.css;\n }\n if (clientFile) {\n templates.push(template(fileName, assetUrl(clientFile?.file, assetBase)));\n modulePreloadManifest[fileName] = collectModulePreloads(\n manifest,\n `app/views/${fileName}.tsx`,\n assetBase,\n );\n }\n }\n\n const loaders = `{${templates.join(\",\")}}`;\n\n // Booted from the bootstrap script rather than through React's\n // `bootstrapModules`, which would preload it at `fetchPriority=\"low\"` — see\n // `clientEntry` in `ViewRouteDispatcher`.\n const clientEntry = createClientEntry(manifest, assetBase);\n if (!clientEntry) {\n // Loudly, but without dying: this is boot, and an incomplete `dist/` must\n // not cost the API routes and static assets too.\n console.error(\n `Client manifest has no \"${CLIENT_ENTRY_KEY}\" entry — dist/client is incomplete, so documents will render but never hydrate.`,\n );\n }\n\n // Vite's manifest `css` field is a `string[]` — a client entry can emit more\n // than one CSS chunk. Read and concatenate them all instead of interpolating\n // the array into a single path (which only works for a one-element array).\n const appCssFiles: string[] = manifest[\"app/client.tsx\"]?.css ?? [];\n const appCSSContent = (\n await Promise.all(appCssFiles.map((cssFile) => Bun.file(`${distDir}/client/${cssFile}`).text()))\n ).join(\"\\n\");\n\n // The island entries (`<module>?gemi-island`) a static view's document\n // loads; see `injectIslands`.\n const resolveIsland = createIslandResolver(manifest, assetBase);\n\n // Identifies this client build: a static page with `navigation` swaps in\n // only pages of the same build. The manifest names every content-hashed\n // file, so it changes whenever any of them does.\n const buildId = createHash(\"sha256\")\n .update(await Bun.file(`${distDir}/client/.vite/manifest.json`).text())\n .digest(\"base64url\")\n .slice(0, 12);\n\n // A static view's CSS comes from the whole import closure of its views (and\n // its own layout's, which then replaces the app stylesheet): there is no\n // client to fetch a shared chunk's CSS later, as a hydrated page's chunk\n // loader does. Read once per file: the build does not change under a\n // running server.\n const cssFileCache = new Map<string, Promise<string>>();\n const readCss = (file: string) => {\n let text = cssFileCache.get(file);\n if (!text) {\n text = Bun.file(`${distDir}/client/${file}`).text();\n cssFileCache.set(file, text);\n }\n return text;\n };\n const staticStyles = async (views: string[], layout: string | undefined) => {\n const files = new Set<string>();\n for (const view of layout ? [layout, ...views] : views) {\n for (const file of collectCss(manifest, `app/views/${view}.tsx`)) files.add(file);\n }\n const styles = await Promise.all(\n [...files].map(async (file) => ({ id: file, content: await readCss(file) })),\n );\n return createStyles(layout ? styles : [{ content: appCSSContent }, ...styles]);\n };\n\n // Which requests are answered from `dist/client` rather than the app:\n //\n // - `/assets` and everything under it, whatever the extension. Vite writes\n // fonts, GIFs, video and `?url` JSON there too, and an allowlist of\n // extensions always trailed the build — a `.woff2` went to the router\n // and paid for an SSR attempt. The router refuses routes under `/assets`\n // at boot, so there is no app answer to lose, and `staticAssetMiss`\n // turns a miss into the reload stub or a 404.\n // - `/.well-known/*`.\n // - Any other path that names a file the build copied out of `public/`,\n // whatever its extension (#583). Any other path may be an app route, so\n // it is static only when that exact file exists — read once here, since\n // the build output does not change under a running server. A view at\n // `/manifest` keeps its data URL (`/manifest.json`) unless the app also\n // ships `public/manifest.json`, and then the file wins. View data is\n // never a file in `dist/client`, so a client-side navigation pays for no\n // filesystem lookup.\n const publicFiles = await listPublicFiles(clientDir);\n\n // The build assets `gemi build` emitted or carried over, with the\n // precompressed siblings it wrote for each (#789). Read once: the build\n // output does not change under a running server. Without the record (a\n // build made before it existed) every file is served as identity with the\n // old cache policy.\n const staticAssets = new Map<string, StaticFileOptions>();\n for (const [file, encodings] of Object.entries(\n (await readStaticAssetsRecord(clientDir))?.files ?? {},\n )) {\n const path = resolve(clientDir, file);\n if (path.startsWith(clientDir + sep)) {\n staticAssets.set(path, { immutable: true, encodings });\n }\n }\n\n async function requestHandler(req: Request) {\n const { pathname } = new URL(req.url);\n\n if (isApiPath(pathname)) {\n return await handleWithApp(req, pathname);\n }\n\n const distPath = clientFilePath(pathname);\n const isFileRequest =\n isReservedAssetPath(pathname) ||\n pathname.startsWith(\"/.well-known\") ||\n (distPath !== null && publicFiles.has(distPath));\n\n if (isFileRequest) {\n // Served from here whatever the asset base is: a CDN in front of the\n // app uses this origin as the source it fills from. A file, not merely\n // a path that exists: `/assets` itself is `dist/client/assets`, a\n // directory, and streaming one would answer 200 and then fail mid-body.\n //\n // `Bun.file(path).stream()` is lazy — a missing file only throws ENOENT\n // once the body is streamed, which is *after* this handler has returned,\n // with the response already committed as 200. So existence is checked\n // here, per request, even for a path `publicFiles` lists.\n if (!distPath || !(await isFile(distPath))) {\n return staticAssetMiss(pathname) ?? (await handleWithApp(req, pathname));\n }\n\n try {\n return await staticFileResponse(req, distPath, staticAssets.get(distPath));\n } catch (error) {\n app.onException?.(error);\n return new Response(\"Not found\", { status: 404 });\n }\n }\n\n return await handleWithApp(req, pathname);\n }\n\n async function handleWithApp(req: Request, pathname: string) {\n const handler = app.fetch.bind(app);\n\n try {\n const result = await handler(req);\n if (result instanceof Response) {\n return result;\n } else {\n const styles = [];\n\n styles.push({\n content: appCSSContent,\n });\n\n const getStyles = async (\n currentViews: string[] | undefined,\n options?: { static?: boolean; layout?: string },\n ) => {\n // No views is the `404` document (an unmatched path, a missing\n // record, a refusal): it is styled like any other, with the app CSS\n // and the `404` view's own (#951).\n const views = currentViews ?? [\"404\"];\n if (options?.static) {\n return staticStyles(views, options.layout);\n }\n for (const view of views) {\n const clientFile = manifest[`app/views/${view}.tsx`];\n for (const cssFile of clientFile?.css ?? []) {\n const css = Bun.file(`${process.env.DIST_DIR}/client/${cssFile}`);\n styles.push({\n id: cssFile,\n content: await css.text(),\n });\n }\n }\n return createStyles(styles);\n };\n\n return await result({\n getStyles,\n clientEntry,\n modulePreloadManifest,\n loaders,\n viewImportMap,\n viewModules,\n ogMap,\n cssManifest,\n assetBase,\n resolveIsland,\n buildId,\n });\n }\n } catch (err) {\n return unhandledErrorResponse(err, pathname, app.onException);\n }\n }\n\n // Compression is on unless the deployment opts out — set `GEMI_COMPRESSION=off`\n // when a layer in front of the origin already compresses HTML and you would\n // rather not spend origin CPU on it.\n const compressionEnabled = (process.env.GEMI_COMPRESSION ?? \"auto\").toLowerCase() !== \"off\";\n\n // Which `X-Forwarded-For` entries to believe — see `forwardedFor.ts`. Parsed\n // before `Bun.serve` so a bad value fails the boot, not every request.\n const forwardedTrust = parseTrustProxy(process.env.GEMI_TRUST_PROXY);\n\n // The broadcast socket endpoint, when the app declares `route.channels`.\n const sockets = app.sockets();\n if (sockets && forwardedTrust.kind === \"none\") {\n console.warn(\n \"[gemi] Broadcast sockets: GEMI_TRUST_PROXY is not set, so a socket's address is its TCP peer. Behind a proxy or load balancer every client shares the proxy's address, and broadcast.maxConnectionsPerIp caps the whole app per process. Set GEMI_TRUST_PROXY (see forwardedFor) or raise that limit.\",\n );\n }\n\n const server = Bun.serve<any>({\n maxRequestBodySize: 10 * 1024 * 1024 * 1024, // 10 GB\n fetch: async (req, server) => {\n // `requestIP` is null for closed/unix sockets — guard so it never\n // throws before the request is handled.\n applyForwardedTrust(req.headers, server.requestIP(req)?.address ?? null, forwardedTrust);\n // A proxy that proves itself with `route.domains.trustProxy.secret` may\n // name the client in `clientIpHeader`, which then wins.\n app.applyTrustedClientIp(req);\n // Ahead of the instrumentation: an upgrade is not a response to trace,\n // and the hub runs the global middleware itself.\n if (sockets?.matches(req)) {\n return sockets.upgrade(req, server);\n }\n // The app's global middleware goes in front of the static handler as well\n // as the router, so it can refuse `/assets/*` too.\n const res = await instrumentation(req, (req) =>\n app.withGlobalMiddleware(req, requestHandler, (err) =>\n unhandledErrorResponse(err, new URL(req.url).pathname, app.onException),\n ),\n );\n // Applied at the very edge, after instrumentation, so every HTML response\n // goes through the same negotiation — including the ones an app's\n // instrumentation produced itself. Anything that isn't HTML comes back\n // untouched.\n return compressionEnabled ? compressResponse(req, res) : res;\n },\n idleTimeout: serverIdleTimeout(),\n port: process.env.PORT || 5173,\n websocket: sockets?.websocket as WebSocketHandler<any>,\n });\n await sockets?.start(server);\n\n printStartupBanner({ port: server.port, rootDir });\n\n return server;\n}\n",
|
|
6
6
|
"/**\n * Transport-level compression for SSR HTML responses.\n *\n * This lives in the runtime rather than in an app, because every gemi app\n * serves the same shape of response: a streamed `text/html` document whose\n * bulk is critical CSS, markup and the `window.__GEMI_DATA__` hydration\n * payload. Compressing it here means the win is portable across deployments —\n * it does not depend on a CDN being configured to compress on the origin's\n * behalf — and it also cuts origin→edge bandwidth, which edge compression\n * cannot.\n *\n * Compression stays strictly at the transport layer: what the client decodes\n * is byte-for-byte the HTML React rendered, so hydration is unaffected.\n *\n * Deliberately dependency-free apart from `node:zlib`/`node:stream` so it can\n * be unit tested without booting a server.\n */\n\nimport { Duplex } from \"node:stream\";\nimport { constants, createBrotliCompress, createGzip } from \"node:zlib\";\n\n/** The content codings we can produce, in the order we prefer them. */\nexport type ContentEncoding = \"br\" | \"gzip\";\n\n/**\n * Server preference, used to break q-value ties. Brotli comes first: at the\n * quality we use it is both smaller *and* no more expensive than gzip on a\n * representative SSR document (~177 kB → 28.0 kB in ~2.0 ms, versus 31.6 kB in\n * ~2.0 ms for gzip).\n */\nconst SERVER_PREFERENCE: ContentEncoding[] = [\"br\", \"gzip\"];\n\n/**\n * Below this, an encoded body plus its framing overhead is not worth the CPU.\n * Only applied when the response declares a `Content-Length` — a streamed SSR\n * response does not, and buffering one just to measure it would defeat the\n * streaming render.\n */\nconst MIN_COMPRESSIBLE_BYTES = 1024;\n\n/**\n * Brotli quality 5 (of 11). The default, 11, is meant for static assets\n * compressed once at build time — on a streamed document it costs ~175 ms,\n * two orders of magnitude more than quality 5 for ~12% more savings. Quality 5\n * is the usual choice for dynamic content.\n *\n * `LGWIN` 19 caps the window at 512 kB rather than the default 16 MB. An SSR\n * document is far smaller than that, so the ratio is unchanged, and the bound\n * matters under concurrency: the window is allocated per in-flight response.\n */\nconst BROTLI_QUALITY = 5;\nconst BROTLI_WINDOW_BITS = 19;\n\n/** zlib's default level. Level 9 costs measurably more for ~1% on HTML. */\nconst GZIP_LEVEL = 6;\n\nfunction parseQValue(raw: string | undefined): number {\n if (raw === undefined) {\n return 1;\n }\n // `q` is a fixed-point number with at most three decimals (RFC 9110 §12.4.2).\n // Anything else is malformed; treat it as an unqualified \"acceptable\" rather\n // than dropping the encoding, matching what tolerant servers do.\n if (!/^\\d+(\\.\\d{0,3})?$/.test(raw.trim())) {\n return 1;\n }\n const value = Number(raw.trim());\n return Number.isFinite(value) ? Math.min(value, 1) : 1;\n}\n\n/**\n * Parses a request `Accept-Encoding` into `coding -> qvalue`.\n *\n * Returns `null` when the header is *absent*, which is distinct from an empty\n * one. RFC 9110 §12.5.3 says an absent header means any coding is acceptable,\n * but a client that cannot decode and forgets to say so is a broken page, not\n * a slow one — so we treat \"absent\" as \"identity\" and only encode when a\n * client has explicitly opted in. An empty header (`Accept-Encoding:`) is a\n * deliberate \"identity only\" and parses to an empty map.\n */\nexport function parseAcceptEncoding(header: string | null | undefined): Map<string, number> | null {\n if (header === null || header === undefined) {\n return null;\n }\n\n const accepted = new Map<string, number>();\n for (const part of header.split(\",\")) {\n const [rawCoding, ...params] = part.split(\";\");\n const coding = rawCoding.trim().toLowerCase();\n if (coding === \"\") {\n continue;\n }\n\n let q = 1;\n for (const param of params) {\n const separator = param.indexOf(\"=\");\n if (separator === -1) {\n continue;\n }\n if (param.slice(0, separator).trim().toLowerCase() === \"q\") {\n q = parseQValue(param.slice(separator + 1));\n break;\n }\n }\n\n accepted.set(coding, q);\n }\n\n return accepted;\n}\n\n/**\n * Picks the coding to use, or `null` for identity.\n *\n * A `q=0` is a refusal, `*` covers codings the client did not name, and ties\n * fall back to `available`'s order — so a plain `Accept-Encoding: gzip, br`\n * (equal q of 1) resolves to our preference rather than the client's.\n */\nexport function negotiateEncoding(\n header: string | null | undefined,\n available: ContentEncoding[] = SERVER_PREFERENCE,\n): ContentEncoding | null {\n const accepted = parseAcceptEncoding(header);\n if (!accepted) {\n return null;\n }\n\n const wildcard = accepted.get(\"*\");\n let best: ContentEncoding | null = null;\n let bestQ = 0;\n\n for (const coding of available) {\n const q = accepted.get(coding) ?? wildcard ?? 0;\n if (q > bestQ) {\n best = coding;\n bestQ = q;\n }\n }\n\n return best;\n}\n\nfunction hasDirective(headerValue: string | null, directive: string): boolean {\n if (!headerValue) {\n return false;\n }\n // Token match, not substring: `no-transform` must not be found inside\n // something like `x-no-transform`.\n return headerValue\n .split(\",\")\n .some((token) => token.trim().toLowerCase().split(\"=\")[0].trim() === directive);\n}\n\n/**\n * Whether this response's representation is one we may encode — and therefore\n * one that varies by `Accept-Encoding`, even on the requests where we end up\n * sending identity.\n *\n * HTML only, on purpose. JSON view-data and API responses are left alone so\n * that this change cannot alter how they are produced, framed or cached;\n * widening the set is a separate decision.\n */\nexport function isCompressible(method: string, res: Response): boolean {\n // A HEAD response carries no body to encode, and emitting `Content-Encoding`\n // without one would describe a body the client never receives.\n if (method === \"HEAD\") {\n return false;\n }\n\n // 204/304 are bodyless; 206 is a byte range of an already-committed\n // representation and re-encoding it would invalidate its `Content-Range`.\n if (res.status < 200 || res.status === 204 || res.status === 206 || res.status === 304) {\n return false;\n }\n\n if (res.headers.has(\"Content-Range\")) {\n return false;\n }\n\n const contentType = res.headers.get(\"Content-Type\");\n if (!contentType || !/^\\s*text\\/html\\s*(;|$)/i.test(contentType)) {\n return false;\n }\n\n // Only once the status and headers have ruled a range out: on Bun 1.3.14 on\n // Linux, reading `.body` of a Response built on a sliced Blob makes it send\n // past the slice's end (#725), so `.body` is never touched on a non-HTML\n // response this function hands back untouched.\n if (!res.body) {\n return false;\n }\n\n // Already encoded upstream — `identity` is the one value that means \"not\n // encoded\" and is safe to replace.\n const contentEncoding = res.headers.get(\"Content-Encoding\");\n if (contentEncoding && contentEncoding.trim().toLowerCase() !== \"identity\") {\n return false;\n }\n\n // RFC 9110 §5.2.2.6: `no-transform` forbids intermediaries *and* us from\n // changing the representation's encoding.\n if (hasDirective(res.headers.get(\"Cache-Control\"), \"no-transform\")) {\n return false;\n }\n\n const contentLength = res.headers.get(\"Content-Length\");\n if (contentLength !== null && Number(contentLength) < MIN_COMPRESSIBLE_BYTES) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Builds the encoder as a web `TransformStream`-shaped pair.\n *\n * Both codings are driven through `node:zlib` with an explicit per-write flush.\n * Without it the compressor holds output until its internal buffer fills,\n * which would sit on React's shell flush and turn a streamed render into a\n * buffered one — the compression win would be paid for in time to first byte.\n */\nexport function createEncoderStream(encoding: ContentEncoding) {\n const compressor =\n encoding === \"br\"\n ? createBrotliCompress({\n params: {\n [constants.BROTLI_PARAM_QUALITY]: BROTLI_QUALITY,\n [constants.BROTLI_PARAM_LGWIN]: BROTLI_WINDOW_BITS,\n },\n flush: constants.BROTLI_OPERATION_FLUSH,\n })\n : createGzip({ level: GZIP_LEVEL, flush: constants.Z_SYNC_FLUSH });\n\n return Duplex.toWeb(compressor) as unknown as ReadableWritablePair<Uint8Array, Uint8Array>;\n}\n\nfunction withVaryAcceptEncoding(headers: Headers) {\n const vary = headers.get(\"Vary\");\n if (!vary) {\n headers.set(\"Vary\", \"Accept-Encoding\");\n return;\n }\n const tokens = vary.split(\",\").map((token) => token.trim().toLowerCase());\n // `Vary: *` already means \"never reuse for another request\"; narrowing it\n // would be a downgrade.\n if (tokens.includes(\"*\") || tokens.includes(\"accept-encoding\")) {\n return;\n }\n headers.append(\"Vary\", \"Accept-Encoding\");\n}\n\n/**\n * Compresses an SSR HTML response when the client asked for it.\n *\n * Non-HTML responses are returned untouched — same object, same body, so this\n * is safe to apply at the edge of the server for every request.\n *\n * HTML responses always gain `Vary: Accept-Encoding`, whether or not this\n * particular request got an encoded body: without it a shared cache could\n * serve a stored encoded variant to a client that cannot decode it.\n */\nexport function compressResponse(req: Request, res: Response): Response {\n if (!isCompressible(req.method, res)) {\n return res;\n }\n\n const headers = new Headers(res.headers);\n withVaryAcceptEncoding(headers);\n\n const encoding = negotiateEncoding(req.headers.get(\"Accept-Encoding\"));\n if (!encoding) {\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n\n let encoder: ReadableWritablePair<Uint8Array, Uint8Array>;\n try {\n encoder = createEncoderStream(encoding);\n } catch {\n // A compressor we cannot construct is not worth failing a page render\n // over. Serve identity — with the `Vary` we already added.\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n\n headers.set(\"Content-Encoding\", encoding);\n // The length of the identity representation says nothing about the encoded\n // body, and a stale one would truncate the response.\n headers.delete(\"Content-Length\");\n\n return new Response(res.body!.pipeThrough(encoder), {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n}\n",
|
|
7
7
|
"import { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { createUnsatisfiableResponse } from \"../http/createStreamResponse\";\nimport { formatContentRange, parseRangeHeader, resolveRange } from \"../http/range\";\nimport { SIBLING_EXTENSION } from \"../vite/precompressAssets\";\nimport { type ContentEncoding, negotiateEncoding } from \"./compression\";\nimport { generateETag } from \"./generateEtag\";\n\n/**\n * Every file in `dist/client` outside `/assets`, as absolute paths — what\n * Vite copied out of `public/` (#583).\n *\n * A root-level path may be an app route, so the static handler may only take\n * one that names a file the app really ships. This used to be guessed from the\n * extension, and every extension the list missed (`.mp4` on 0.58, `.wasm`,\n * `.csv` …) was answered by the router with a rendered 404 page. The build\n * output is fixed for the life of the process, so the exact set is read once\n * at boot instead.\n *\n * `assets/` is left out because the static handler takes everything under it\n * anyway. A name starting with a dot is left out at any depth: `.vite/` is\n * build metadata (both manifests and the asset base record), and a stray\n * `.DS_Store` or `.env` copied in from `public/` is nothing a request should\n * reach. `/.well-known` is served by its own rule.\n */\nexport async function listPublicFiles(clientDir: string): Promise<Set<string>> {\n const files = new Set<string>();\n\n async function walk(dir: string, topLevel: boolean) {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n if (topLevel && entry.name === \"assets\") continue;\n const path = join(dir, entry.name);\n let isDir = entry.isDirectory();\n let isFile = entry.isFile();\n if (entry.isSymbolicLink()) {\n try {\n const target = await stat(path);\n isDir = target.isDirectory();\n isFile = target.isFile();\n } catch {\n continue;\n }\n }\n if (isFile) files.add(path);\n // A symlinked directory is not followed: a link back up the tree would\n // never end, and `clientFilePath` only names paths inside `dist/client`.\n else if (isDir && !entry.isSymbolicLink()) await walk(path, false);\n }\n }\n\n await walk(clientDir, true);\n return files;\n}\n\nconst LONG_LIVED = \"public, max-age=31536000, must-revalidate\";\n\n/**\n * A content-hashed build asset never changes under its name: a new build\n * writes a new name. So a browser or edge may keep it a year without asking\n * again (#789).\n */\nconst IMMUTABLE = \"public, max-age=31536000, immutable\";\n\nexport interface StaticFileOptions {\n /**\n * The file is a content-hashed build asset (it is in the build's\n * `static-assets.json`), so it is sent as `immutable`.\n */\n immutable?: boolean;\n /**\n * The precompressed siblings the build wrote for it (`file.br`,\n * `file.gz`), in preference order. Offering any makes the response vary by\n * `Accept-Encoding`, so it says so even when it sends identity.\n */\n encodings?: ContentEncoding[];\n}\n\nasync function encodedSibling(\n req: Request,\n path: string,\n encodings: ContentEncoding[],\n): Promise<{ encoding: ContentEncoding; path: string; size: number } | null> {\n // A range is a byte range of the identity representation: an encoded\n // variant has no stable offsets to slice, so a range request gets identity.\n if (encodings.length === 0 || req.headers.has(\"Range\")) {\n return null;\n }\n const encoding = negotiateEncoding(req.headers.get(\"Accept-Encoding\"), encodings);\n if (!encoding) {\n return null;\n }\n const siblingPath = path + SIBLING_EXTENSION[encoding];\n try {\n // Checked per request, like the file itself: a sibling deleted under a\n // running server would otherwise fail mid-body after a committed 200.\n const info = await stat(siblingPath);\n return info.isFile() ? { encoding, path: siblingPath, size: info.size } : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The response for a file in `dist/client` that is known to exist.\n *\n * Honours a single byte `Range`, so a `<video>` pointed at a public `.mp4`\n * can seek — Safari will not play one at all from a server that answers its\n * `bytes=0-1` probe with the whole file. Recent Bun releases slice a\n * file-backed 200 themselves; doing it here keeps that from depending on the\n * Bun version a deployment runs, and answers an unsatisfiable range with the\n * same 416 the `stream()` responses use.\n *\n * A build asset with precompressed siblings is sent as the one\n * `Accept-Encoding` prefers (brotli, then gzip, then identity), with the\n * original's `Content-Type`. Nothing is compressed here: a file without a\n * sibling is sent as is.\n *\n * `Bun.file().type` is the mime lookup, and is `application/octet-stream` for\n * an extension it does not know.\n */\nexport async function staticFileResponse(\n req: Request,\n path: string,\n options: StaticFileOptions = {},\n): Promise<Response> {\n const file = Bun.file(path);\n const encodings = options.encodings ?? [];\n\n const headers: Record<string, string> = {\n \"Content-Type\": file.type || \"application/octet-stream\",\n \"Cache-Control\": options.immutable ? IMMUTABLE : LONG_LIVED,\n \"Accept-Ranges\": \"bytes\",\n };\n if (encodings.length > 0) {\n headers.Vary = \"Accept-Encoding\";\n }\n\n const sibling = await encodedSibling(req, path, encodings);\n if (sibling) {\n const encoded = Bun.file(sibling.path);\n return new Response(encoded.stream(), {\n headers: {\n ...headers,\n \"Content-Encoding\": sibling.encoding,\n \"Content-Length\": String(sibling.size),\n // A different representation, so a different validator.\n ETag: `${generateETag(encoded.lastModified)}-${sibling.encoding}`,\n },\n });\n }\n\n const total = file.size;\n headers.ETag = generateETag(file.lastModified);\n\n const range = parseRangeHeader(req.headers.get(\"Range\"));\n\n if (range) {\n const resolved = resolveRange(range, total);\n if (!resolved) {\n return createUnsatisfiableResponse(total);\n }\n return new Response(file.slice(resolved.start, resolved.end + 1).stream(), {\n status: 206,\n headers: {\n ...headers,\n \"Content-Range\": formatContentRange(resolved.start, resolved.end, total),\n \"Content-Length\": String(resolved.length),\n },\n });\n }\n\n return new Response(file.stream(), {\n headers: { ...headers, \"Content-Length\": String(total) },\n });\n}\n",
|
|
8
8
|
"import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { availableParallelism } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { brotliCompress, constants, gzip } from \"node:zlib\";\n\nimport type { ContentEncoding } from \"../server/compression\";\n\n/**\n * Build-time compression of the client bundle (#789).\n *\n * `gemi start` compresses SSR HTML per request, but a hashed chunk is the same\n * bytes for the life of a release, so compressing it per request would spend\n * origin CPU on every visitor for a result that never changes. `gemi build`\n * writes `file.br` (brotli quality 11) and `file.gz` (gzip level 9) next to\n * every compressible build asset instead, once, and the server picks one by\n * `Accept-Encoding`.\n *\n * What the build emitted (and what it carried over from earlier releases, see\n * `previousAssets.ts`) is recorded in `dist/client/.vite/static-assets.json`,\n * with the encodings each file has. The server reads it at boot: a file in the\n * record is a content-hashed build asset, so it is sent as `immutable`, and\n * only the encodings listed for it are offered. A file not in the record (one\n * copied from `public/assets/`, or anything in a build made before this\n * existed) is served as before.\n */\n\n/** Relative to `dist/client`; beside Vite's own manifest, so never served. */\nexport const STATIC_ASSETS_RECORD = \".vite/static-assets.json\";\n\n/**\n * Below this, the encoded body plus its header is not worth a second request\n * representation: a 600-byte chunk saves a few hundred bytes at most, and\n * every file with siblings adds a `stat` per request.\n */\nexport const PRECOMPRESS_MIN_BYTES = 1024;\n\n/**\n * Text-like formats that compress well. Already-compressed formats (images,\n * `woff`/`woff2`, video) are left out: brotli shaves a percent or two off\n * them at best. Source maps are left out because a page never loads one, and\n * only devtools would gain from the extra build time.\n */\nconst COMPRESSIBLE = /\\.(?:m?js|cjs|css|svg|json|webmanifest|wasm|txt|xml|ttf|otf|eot)$/i;\n\nconst TEXT = /\\.(?:m?js|cjs|css|svg|json|webmanifest|txt|xml)$/i;\nconst FONT = /\\.(?:ttf|otf|eot)$/i;\n\nexport const SIBLING_EXTENSION: Record<ContentEncoding, string> = {\n br: \".br\",\n gzip: \".gz\",\n};\n\n/** Server preference: brotli is ~15% smaller than gzip -9 on a JS chunk. */\nconst ENCODINGS: ContentEncoding[] = [\"br\", \"gzip\"];\n\nexport interface StaticAssetsRecord {\n /**\n * Path relative to `dist/client` (`assets/client-DJhrQPW5.js`) → the\n * precompressed siblings it has, in preference order. An empty list is a\n * build asset that is served as identity only (an image, a small file).\n */\n files: Record<string, ContentEncoding[]>;\n}\n\nexport interface PrecompressStats {\n files: number;\n identityBytes: number;\n brBytes: number;\n gzipBytes: number;\n}\n\nconst brotli = promisify(brotliCompress);\nconst gzipAsync = promisify(gzip);\n\nexport function isPrecompressible(file: string): boolean {\n return COMPRESSIBLE.test(file);\n}\n\n/** `true` for `x.js.br` / `x.js.gz`: a sibling, not an asset of its own. */\nexport function isEncodedSibling(file: string): boolean {\n return file.endsWith(\".br\") || file.endsWith(\".gz\");\n}\n\nasync function encode(encoding: ContentEncoding, file: string, input: Buffer): Promise<Buffer> {\n if (encoding === \"gzip\") {\n return gzipAsync(input, { level: 9 });\n }\n return brotli(input, {\n params: {\n [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,\n [constants.BROTLI_PARAM_MODE]: TEXT.test(file)\n ? constants.BROTLI_MODE_TEXT\n : FONT.test(file)\n ? constants.BROTLI_MODE_FONT\n : constants.BROTLI_MODE_GENERIC,\n [constants.BROTLI_PARAM_SIZE_HINT]: input.length,\n },\n });\n}\n\nasync function fileSize(path: string): Promise<number | null> {\n try {\n const info = await stat(path);\n return info.isFile() ? info.size : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Writes the `.br`/`.gz` siblings of `files` (paths relative to `clientDir`)\n * and returns the encodings each one ended up with.\n *\n * A sibling is only kept when it is smaller than the file itself; one that is\n * not would cost bytes on the wire, so the file is served as identity for\n * that encoding. With `reuseExisting`, a sibling already on disk is trusted\n * rather than rebuilt — for files carried from an earlier release, whose\n * siblings were copied with them and match by content hash.\n */\nexport async function precompressAssets(\n clientDir: string,\n files: Iterable<string>,\n options: {\n reuseExisting?: boolean;\n minBytes?: number;\n concurrency?: number;\n } = {},\n): Promise<{ record: StaticAssetsRecord; stats: PrecompressStats }> {\n const minBytes = options.minBytes ?? PRECOMPRESS_MIN_BYTES;\n const record: StaticAssetsRecord = { files: {} };\n const stats: PrecompressStats = {\n files: 0,\n identityBytes: 0,\n brBytes: 0,\n gzipBytes: 0,\n };\n\n const queue = [...new Set(files)];\n for (const file of queue) {\n record.files[file] = [];\n }\n\n async function work(file: string) {\n if (!isPrecompressible(file)) {\n return;\n }\n const path = join(clientDir, file);\n const size = await fileSize(path);\n if (size === null || size < minBytes) {\n return;\n }\n\n let input: Buffer | null = null;\n const available: ContentEncoding[] = [];\n const sizes: Partial<Record<ContentEncoding, number>> = {};\n for (const encoding of ENCODINGS) {\n const siblingPath = path + SIBLING_EXTENSION[encoding];\n const existing = options.reuseExisting ? await fileSize(siblingPath) : null;\n if (existing !== null) {\n if (existing < size) {\n available.push(encoding);\n sizes[encoding] = existing;\n }\n continue;\n }\n input ??= await readFile(path);\n const output = await encode(encoding, file, input);\n if (output.length < size) {\n await writeFile(siblingPath, output);\n available.push(encoding);\n sizes[encoding] = output.length;\n }\n }\n\n record.files[file] = available;\n if (available.length > 0) {\n stats.files += 1;\n stats.identityBytes += size;\n stats.brBytes += sizes.br ?? size;\n stats.gzipBytes += sizes.gzip ?? size;\n }\n }\n\n // zlib's async calls run on the libuv thread pool; a few files in flight\n // keep it busy without reading the whole bundle into memory at once.\n const concurrency = Math.max(1, options.concurrency ?? Math.min(8, availableParallelism()));\n let next = 0;\n await Promise.all(\n Array.from({ length: Math.min(concurrency, queue.length) }, async () => {\n while (next < queue.length) {\n await work(queue[next++]);\n }\n }),\n );\n\n return { record, stats };\n}\n\nexport async function writeStaticAssetsRecord(clientDir: string, record: StaticAssetsRecord) {\n const path = join(clientDir, STATIC_ASSETS_RECORD);\n await mkdir(dirname(path), { recursive: true });\n // Sorted, so two builds of the same tree write the same file.\n const files = Object.fromEntries(\n Object.entries(record.files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n await writeFile(path, `${JSON.stringify({ files }, null, 2)}\\n`);\n}\n\n/**\n * The record a build wrote, or `null` when there is none (a build made before\n * precompression, or an incomplete `dist/`): the server then serves every file\n * as before. A malformed entry is dropped rather than trusted.\n */\nexport async function readStaticAssetsRecord(\n clientDir: string,\n): Promise<StaticAssetsRecord | null> {\n let raw: string;\n try {\n raw = await readFile(join(clientDir, STATIC_ASSETS_RECORD), \"utf8\");\n } catch {\n return null;\n }\n let files: unknown;\n try {\n files = JSON.parse(raw)?.files;\n } catch {\n return null;\n }\n if (!files || typeof files !== \"object\") {\n return null;\n }\n const record: StaticAssetsRecord = { files: {} };\n for (const [file, encodings] of Object.entries(files)) {\n if (!Array.isArray(encodings)) continue;\n record.files[file] = ENCODINGS.filter((encoding) => encodings.includes(encoding));\n }\n return record;\n}\n",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"import { stringEraRefusal } from \"../http/Error\";\nimport { isApiPath } from \"../services/router/apiPath\";\n\n// The production server's last resort: an error nothing on the way here turned\n// into a response. Request breakers (auth, 404s, 416s) are answered by the\n// dispatchers before this, so what arrives is a genuine failure — and its\n// message and stack are for the developer, not for whoever triggered it. They\n// can carry absolute paths, dependency versions, SQL fragments and column\n// names. The dev server renders them on purpose (`httpDev.ts`); this does not.\n\nconst GENERIC_500_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 0; min-height: 100vh; display: grid; place-items: center; }\n main { text-align: center; padding: 16px; }\n h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }\n p { margin: 0; opacity: 0.7; }\n </style>\n </head>\n <body>\n <main>\n <h1>Something went wrong</h1>\n <p>Please try again later.</p>\n </main>\n </body>\n</html>`;\n\nexport function unhandledErrorResponse(\n err: unknown,\n pathname: string,\n onException?: (error: Error) => void,\n): Response {\n // `onException` is the report, and the default one `App` installs is a\n // `console.error`, so logging here as well printed every failure twice\n // (three times for `/api`, whose dispatcher logs before rethrowing). Only\n // without a reporter does this log itself.\n //\n // Reported for `/api` too: an app's error tracking should see an API failure\n // that got this far, not only a page one. `onRequestFail` is a different\n // hook (route config, called by the dispatchers), so this is not a second\n // report through the same channel.\n if (!onException) {\n console.error(err);\n } else {\n try {\n // The hook is typed to receive an `Error`; a thrown string or object is\n // wrapped so a reporter reading `.stack` or `.name` gets one.\n onException(err instanceof Error ? err : new Error(String(err), { cause: err }));\n } catch (reportError) {\n // A broken reporter must not turn a 500 into a leaked stack either.\n console.error(err);\n console.error(reportError);\n }\n }\n\n if (isApiPath(pathname)) {\n return new Response(\n JSON.stringify({ error: stringEraRefusal(\"server_error\", \"Internal Server Error\", 500) }),\n {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n },\n );\n }\n\n return new Response(GENERIC_500_HTML, {\n status: 500,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n",
|
|
14
14
|
"/**\n * Which `X-Forwarded-For` entries the production server believes (#8).\n *\n * `X-Forwarded-For` is written by whoever sends the request. A client talking\n * to the app directly — or through a proxy that appends to the header rather\n * than replacing it — picks any address it likes, and `clientIp` (the rate\n * limiter's default key) used to read the left-most entry, which is exactly\n * the one the client wrote. So the server decides here, once, at the edge,\n * and hands the app a single address it can rely on:\n *\n * - `none` (the default): no proxy is trusted. The header is replaced with the\n * socket's peer address, and `X-Real-IP` is dropped.\n * - `hops` (`GEMI_TRUST_PROXY=<n>`): the `n` proxies nearest the app are\n * trusted. Each appends the address it was reached from, so the client is\n * the `n`-th entry from the right of `X-Forwarded-For`. Anything\n * further left was written by someone the app does not trust and is dropped.\n * - `all` (`GEMI_TRUST_PROXY=true`): the inbound header is passed through\n * unchanged, as before 0.88. Only sound behind a proxy that overwrites\n * `X-Forwarded-For` (and `X-Real-IP`) rather than appending to it.\n */\nexport type ForwardedTrust =\n | { kind: \"none\" }\n | { kind: \"all\" }\n | { kind: \"hops\"; hops: number };\n\nconst NONE: ForwardedTrust = { kind: \"none\" };\n\n/**\n * `GEMI_TRUST_PROXY` as a policy. Unset, empty, `false`, `off` and `0` trust\n * nothing; `true` trusts everything; a positive integer is a hop count.\n * Anything else throws, so a typo fails the boot instead of silently trusting\n * nothing (every client sharing the proxy's address) or everything.\n */\nexport function parseTrustProxy(value: string | undefined): ForwardedTrust {\n const raw = (value ?? \"\").trim().toLowerCase();\n if (raw === \"\" || raw === \"false\" || raw === \"off\" || raw === \"0\") {\n return NONE;\n }\n if (raw === \"true\") {\n return { kind: \"all\" };\n }\n if (/^[1-9]\\d*$/.test(raw)) {\n return { kind: \"hops\", hops: Number(raw) };\n }\n throw new Error(\n `GEMI_TRUST_PROXY must be \"false\", \"true\" or the number of proxies in front of the app (e.g. 1). Got ${JSON.stringify(value)}.`,\n );\n}\n\n/**\n * Rewrites `headers` so `X-Forwarded-For` holds what `trust` says the client\n * address is. `peer` is the socket's remote address — `null` for a unix\n * socket, where the immediate hop is still counted as one proxy.\n */\nexport function applyForwardedTrust(\n headers: Headers,\n peer: string | null,\n trust: ForwardedTrust,\n): void {\n if (trust.kind === \"all\") {\n if (!headers.get(\"x-forwarded-for\") && peer) {\n headers.set(\"x-forwarded-for\", peer);\n }\n return;\n }\n\n // Under any other policy `X-Real-IP` is as client-written as the rest; the\n // address the app should use is the one left in `X-Forwarded-For`.\n headers.delete(\"x-real-ip\");\n\n let client: string | null = peer;\n if (trust.kind === \"hops\") {\n const entries = (headers.get(\"x-forwarded-for\") ?? \"\")\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n // The chain is `entries` + `peer`; the client sits `hops` places left of\n // its end, i.e. `entries[entries.length - hops]`. A shorter chain than\n // expected means the request skipped a proxy, so its left-most hop is the\n // furthest thing seen — never anything further left than that.\n const index = entries.length - trust.hops;\n client = index >= 0 ? entries[index]! : (entries[0] ?? peer);\n }\n\n if (client) {\n headers.set(\"x-forwarded-for\", client);\n } else {\n headers.delete(\"x-forwarded-for\");\n }\n}\n"
|
|
15
15
|
],
|
|
16
|
-
"mappings": "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",
|
|
17
|
-
"debugId": "
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "AF73DF72B0CE2E5964756E2164756E21",
|
|
18
18
|
"names": []
|
|
19
19
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{$b as M9,Jc as F9,Xb as k9,_b as K3,dc as e,ld as N9,qc as U9,wc as J9}from"./chunk-7dwgex81.js";import{Bd as I$,sd as c3,ud as B3,xd as d9,yd as c9,zd as Q$}from"./chunk-r1x5ms0y.js";import{Ed as w9,Jd as v9,Ld as g9,Sd as x,Td as h9,Ud as m9,Wd as u9,Xd as N$}from"./chunk-6zz3kez5.js";import{_d as N}from"./chunk-6hs102jh.js";import{Bf as X$,Cf as z$,Cg as W$,Df as G$,Dg as Y$,Ee as V$,Fe as p3,Ff as w,Je as J3,Jg as _$,Ke as X3,Sf as a3,bg as s3,cf as m3,dg as n,hg as o3,ig as t9,jg as s9,ke as l,ne as T9,pg as f3,qf as Z3,qg as o9,re as D$,rg as G3,sg as r9,tg as e9,ue as K$,ug as w3,vg as a,wf as H3,wg as L3,xf as h3,xg as d3,yf as q}from"./chunk-bywhpfyv.js";import{Oh as p,Qh as m,di as t3,ri as H9,vi as P3,wi as F3,xi as z3}from"./chunk-eve0hewa.js";import{Di as i9}from"./chunk-v2cv3wcw.js";import{Oi as g}from"./chunk-7hdfbp2x.js";import{Ui as u3}from"./chunk-dzvp8ynr.js";import{$i as N3,Xi as H$,Yi as i3,aj as n3,dj as J$}from"./chunk-23mcs80p.js";import{rj as l9,wj as n9}from"./chunk-e9xbdgb2.js";import{Mj as f9,Oj as u}from"./chunk-98dxtkbt.js";import{Pj as j}from"./chunk-ks7msv3e.js";import{Vj as P$}from"./chunk-yxjfghq3.js";import{Jk as l3}from"./chunk-t7qp951h.js";import{Qk as U3,Tk as y}from"./chunk-kddgfct7.js";import{al as g3}from"./chunk-eyqsvzej.js";import{ll as a9}from"./chunk-5aj27bgc.js";var x$="mcp",A$=/^[\x21\x23-\x5B\x5D-\x7E]+$/;class D extends e{status;constructor($,Z){super($);this.status=Z;this.name="McpToolError"}}class v extends Error{reason;constructor($,Z){super($);this.reason=Z;this.name="McpCallRefusedError"}}var r3=/^[A-Za-z0-9_-]{1,64}$/,C$=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,e3=4000,T=1e5,S$=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i,B9=new WeakMap;function G6($){return B9.get($)}class i{router;dispatcher;static token="router.mcp";plans=new Map;scopeTools=new Map;files;featureOn;constructor($,Z,z={}){this.router=$;this.dispatcher=Z;this.files=R$(z.files),this.featureOn=z.featureOn??X3;for(let[G,W]of Object.entries($.routes??{}))this.plans.set(G,q$(W)?this.planNative(G,W):this.plan(G,W));this.resolveScopes()}list($,Z){if(D3($),$.kind==="local")return this.descriptors(Z);let z=[];for(let[G,W]of this.plans){if(!W.remote||!this.grants($.principal,G))continue;if(W9(W.remote,Z))z.push(W.remote)}return z}async available($,Z){let z=[];for(let G of this.list($,Z))if(await this.isOn(G))z.push(G);return z}descriptors($){let Z=[];for(let{descriptor:z}of this.plans.values())if(W9(z,$))Z.push(z);return Z}get maxFileBytes(){return this.files.maxBytes}scopes(){return[...this.scopeTools].map(([$,{description:Z}])=>({name:$,description:Z}))}scopesFor($){return[...this.scopeTools].filter(([,{tools:Z}])=>Z.has($)).map(([Z])=>Z)}async execute($,Z,z,G,W={}){D3($);let X=this.plans.get(Z);if(X&&!await this.isOn(X.descriptor)){if($.kind==="remote")throw new v(`There is no tool named "${Z}".`,"unknown-tool");throw new D(`There is no tool named "${Z}".`,404)}if($.kind==="remote"){if(!X?.remote||!this.grants($.principal,Z))throw new v(`There is no tool named "${Z}".`,"unknown-tool");if(X.descriptor.requiresApproval&&W.approved!==!0&&!W3(X))throw new v(`"${Z}" needs the user's approval, and this call does not have it.`,"approval-required");G=void 0}if(!X)throw Error(`McpRegistry: there is no tool named "${Z}".`);if(X.kind==="tool")return await this.executeNative(X,$,z,G,W);let Y=X,K=$.kind==="remote"?Y.remote:Y.descriptor,Q=K.inputSchema.safeParse(z);if(Q.ok===!1)throw new D(`Invalid arguments for "${Z}": ${Q.errors.join(", ")}`);let V=Q.value,H={caller:$,req:$.req,tool:K,input:V,ctx:G},U=await this.fillPath(Y,H),{path:F}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${U}`));if(F!==K.url)throw new D(`"${Z}" has nothing at ${U}.`,404);let O={};for(let L of Y.jsonKeys)if(V[L]!==void 0)O[L]=V[L];let B,I="";if(Y.fileFields.length>0)B=await this.formData(Y,O,H);else if(K.method==="GET")I=u$(O);else if(Y.jsonKeys.length>0)B=O;let k=this.router.credentials?await this.bind(Y,"the credentials",()=>this.router.credentials(H)):void 0,A={...k?{credentials:k}:{},...W.signal?{signal:W.signal}:{},...W.progress?{progress:W.progress}:{},...$.kind==="remote"?{identity:{user:$.principal.user,grant:{via:$.principal.via,id:$.principal.id,scopes:$.principal.scopes,...$.principal.clientId?{clientId:$.principal.clientId}:{}}}}:{}},M;try{let L=I?`${U}?${I}`:U;M=Object.keys(A).length>0?await this.dispatcher.dispatchAs($.req,K.method,L,B,A):await this.dispatcher.dispatchAs($.req,K.method,L,B)}catch(L){throw console.error(`[gemi/mcp] Dispatching "${Z}" failed:`,L),new D(`"${Z}" failed on the server.`,500)}if(!Y.result&&!K.outputSchema)return await V9(Z,M);return await this.project(Y,H,M)}async isOn($){return $.feature===void 0||await this.featureOn($.feature)===!0}plan($,Z){if(!r3.test($))throw Error(`McpRouter: "${$}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let z=`McpRouter: "${$}" (${Z?.method} ${Z?.url})`;if(!(Z instanceof Object)||Z.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${$}" is not a route or a tool. Declare it with this.fromApiRoute(...) or this.fromTool(...).`);let{method:G,url:W,meta:X}=Z;if(X.feature!==void 0&&(typeof X.feature!=="string"||X.feature===""))throw Error(`${z}: feature must be a feature key from app/features.`);if(W.startsWith("/__gemi__"))throw Error(`${z} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[W]?.[G];if(!Y)throw Error(`${z} names no route of this app. Expose a route by the path and verb it is mounted at.`);let K=[...W.matchAll(C$)].map(([,J,C])=>({name:J,modifier:C})),Q=X.params??{};for(let J of K)if(!(J.name in Q))throw Error(`${z}: the path param "${J.name}" must be bound or declared "input".`);for(let[J,C]of Object.entries(Q)){if(!K.some((h)=>h.name===J))throw Error(`${z}: "${J}" in params is not a param of the url.`);if(Y9(C))w$(z,`params.${J}`,C);else X9(z,`params.${J}`,C)}let V=[],H;if(X.input){if(H=X.input.toJSONSchema(),H.type!=="object"||!H.properties)throw Error(`${z}: input must be an s.object(...), the fields of the request body.`);V=Object.keys(H.properties)}let U=Object.entries(X.files??{}).map(([J,C])=>({name:J,binder:C}));for(let J of U)X9(z,`files.${J.name}`,J.binder);if(X.result!==void 0&&typeof X.result!=="function")throw Error(`${z}: result must be a function.`);if(X.output!==void 0&&typeof X.output?.safeParse!=="function")throw Error(`${z}: output must be a schema, built with s.`);if(X.title!==void 0&&(typeof X.title!=="string"||X.title===""))throw Error(`${z}: title must be a non-empty string.`);let F=K9(z,G,X.annotations);if(U.length>0&&G==="GET")throw Error(`${z}: a GET has no body to carry a file.`);let O={},B=(J,C)=>{if(J in O)throw Error(`${z}: "${J}" is the input key of two params or files, and the model can only send one.`);O[J]=C},I=new Map,k=new Map;for(let J of K){let C=Q[J.name];if(C==="input"){let h=p.string().describe(`The ":${J.name}" segment of the url.`);B(J.name,J.modifier==="?"||J.modifier==="*"?h.optional():h)}else if(Y9(C)){let h=C.as??J.name;B(h,C.input),k.set(J.name,{key:h,param:C})}else I.set(J.name,C)}let A={};for(let J of U)if(J.binder==="input")B(J.name,p.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.")),A[J.name]=this.remoteFileSchema();let M=[...Object.keys(O),...U.map((J)=>J.name)];for(let J of new Set(M))if(V.includes(J))throw Error(`${z}: "${J}" is both an input field and a param or file, and the model can only send one.`);let L=Object.freeze({kind:"route",name:$,description:X.description,method:G,url:W,inputSchema:Q9(X.input,H,p.object(O)),...X.output?{outputSchema:X.output}:{},...X.title?{title:X.title}:{},annotations:F,tags:Object.freeze([...X.tags??[]]),requiresApproval:X.requiresApproval===!0,...X.feature!==void 0?{feature:X.feature}:{},...Y.source?{source:Y.source}:{}}),r=!U.every((J)=>J.binder==="input")?null:U.length===0?L:Object.freeze({...L,inputSchema:Q9(X.input,H,p.object(h$(O,Object.keys(A))),A)});return{kind:"route",descriptor:L,remote:r,params:K,paramBinders:I,modelParams:k,fileFields:U,jsonKeys:V,...X.result?{result:X.result}:{}}}remoteFileSchema(){let{maxBytes:$,fetch:Z}=this.files,z={name:{type:"string",description:"The file's name, like report.pdf."},mimeType:{type:"string",description:"The file's media type, like application/pdf."},data:{type:"string",contentEncoding:"base64",description:`The file's bytes, base64-encoded (RFC 4648, standard alphabet). At most ${$} bytes decoded.`}};if(Z)z.url={type:"string",format:"uri",description:"An https URL the server downloads the file from, instead of data."};let G={type:"object",description:Z?"A file, sent as base64 in data or fetched from url: exactly one of the two.":"A file, sent as base64 in data.",properties:z,required:["name","mimeType"],additionalProperties:!1},W=Object.keys(z),X=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["a file must be an object"]};let K=[],Q={};for(let V of Object.keys(Y))if(!W.includes(V))K.push(`a file has no field "${V}"`);for(let V of W){let H=Y[V];if(H===void 0||H===null){if(V==="name"||V==="mimeType")K.push(`a file needs "${V}"`);continue}if(typeof H!=="string")K.push(`a file's "${V}" must be a string`);else Q[V]=H}return K.length>0?{ok:!1,errors:K}:{ok:!0,value:Q}};return{toJSONSchema:()=>G,safeParse:X,parse(Y){let K=X(Y);if(K.ok===!1)throw Error(K.errors.join(", "));return K.value}}}planNative($,Z){if(!r3.test($))throw Error(`McpRouter: "${$}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let z=`McpRouter: "${$}" (a native tool)`,{tool:G,meta:W={}}=Z;if(!(G instanceof Object)||typeof G.name!=="string")throw Error(`${z} is not an AgentTool. Pass one built with AgentTool.create(...).`);if(G.answeredBy==="client"||typeof G.execute!=="function")throw Error(`${z}: "${G.name}" is answered by the client, and only a tool the server runs can be called through the registry.`);if(typeof G.inputSchema==="function")throw Error(`${z}: "${G.name}" resolves its input schema per run, and the registry lists one schema to every caller. Give it a fixed inputSchema.`);if(typeof G.inputSchema?.safeParse!=="function")throw Error(`${z}: its inputSchema must be a schema, built with s.`);if(typeof W!=="object"||W===null)throw Error(`${z}: the meta must be an object.`);if(W.description!==void 0&&(typeof W.description!=="string"||W.description.trim()===""))throw Error(`${z}: description must be a non-empty string.`);if(W.title!==void 0&&(typeof W.title!=="string"||W.title===""))throw Error(`${z}: title must be a non-empty string.`);if(W.tags!==void 0&&(!Array.isArray(W.tags)||W.tags.some((Q)=>typeof Q!=="string")))throw Error(`${z}: tags must be an array of strings.`);if(W.requiresApproval===!1&&G.requiresApproval)throw Error(`${z}: "${G.name}" requires approval, and requiresApproval: false would run it without asking. Leave it out, or register a tool that does not ask.`);let X=W.remote;if(X!==void 0){if(typeof X!=="object"||X===null||typeof X.context!=="function")throw Error(`${z}: remote must be { context(call) }, building the AgentContext a remote call runs with.`);if(X.progress!==void 0&&typeof X.progress!=="function")throw Error(`${z}: remote.progress must be a function.`)}let Y=W.description??G.description;if(typeof Y!=="string"||Y.trim()==="")throw Error(`${z} needs a description: the tool's own, or the meta's.`);let K=Object.freeze({kind:"tool",name:$,description:Y,inputSchema:G.inputSchema,...G.outputSchema?{outputSchema:G.outputSchema}:{},...W.title?{title:W.title}:{},annotations:K9(z,void 0,W.annotations),tags:Object.freeze([...W.tags??[]]),requiresApproval:G.requiresApproval||W.requiresApproval===!0});return{kind:"tool",descriptor:K,remote:X?K:null,tool:G,meta:W}}async approvalRequired($,Z,z,G={}){let W=this.plans.get(Z);if(!W)return!0;if(!W3(W))return W.descriptor.requiresApproval;let X=W;if($.kind!=="remote"||!X.meta.remote)return!0;let Y=X.descriptor.inputSchema.safeParse(z);if(Y.ok===!1)return!0;let K={caller:$,req:$.req,tool:X.descriptor,input:Y.value},Q;try{Q=await this.bind(X,"the remote context",()=>X.meta.remote.context(K))}catch{return!0}if(typeof Q!=="object"||Q===null)return!0;let V=G.signal??new AbortController().signal;return G9(X,Y.value,z9(Z9(X,Q,V)))}nativeApproval($){let Z=this.plans.get($);if(Z?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${$}".`);return W3(Z)??Z.descriptor.requiresApproval}nativeTool($){let Z=this.plans.get($);if(Z?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${$}".`);return Z.tool}startNative($,Z,z,G){let W=this.plans.get(Z);if(W?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${Z}".`);if($===void 0)return W.tool.execute(z,G);if(D3($),$.kind!=="local")throw Error("McpRegistry: startNative is for a local caller. A remote call goes through execute().");let X=W.descriptor.inputSchema.safeParse(z);if(X.ok===!1)throw new D(`Invalid arguments for "${Z}": ${X.errors.join(", ")}`);return this.runNative(W,{caller:$,req:$.req,tool:W.descriptor,input:X.value},G)}runNative($,Z,z){let G={...z};return B9.set(G,{...Z,ctx:G}),$.tool.execute(Z.input,G)}async executeNative($,Z,z,G,W){let{name:X}=$.descriptor,Y=$.descriptor.inputSchema.safeParse(z);if(Y.ok===!1)throw new D(`Invalid arguments for "${X}": ${Y.errors.join(", ")}`);let K={caller:Z,req:Z.req,tool:$.descriptor,input:Y.value},Q=(L,_3)=>{if(!W.progress)return;let r;try{r=$.meta.remote?.progress?$.meta.remote.progress(L,_3):k$(L,_3)}catch(J){console.error(`[gemi/mcp] The progress mapping of "${X}" failed:`,J);return}if(r)W.progress(r)};if(Z.kind==="local"){if(!G)throw Error(`McpRegistry: "${X}" is a native tool, which runs with the tool context of an agent call, and none was passed to execute().`);return await $9(this.runNative($,{...K,ctx:G},G),Q)}let V=$.meta.remote,H=await this.bind($,"the remote context",()=>V.context(K));if(typeof H!=="object"||H===null)throw console.error(`[gemi/mcp] The remote context of "${X}" is not an object:`,H),new D(`"${X}" failed on the server.`,500);let U=$.tool.timeoutMs,F=[...W.signal?[W.signal]:[],...U!==void 0?[AbortSignal.timeout(U)]:[]],O=F.length===0?new AbortController().signal:F.length===1?F[0]:AbortSignal.any(F),B=Z9($,H,O);if(!W.approved&&W3($)&&await G9($,K.input,z9(B)))throw new v(`"${X}" needs the user's approval, and this call does not have it.`,"approval-required");let I;try{I=await $9(this.runNative($,{...K,ctx:B},B),Q,O)}catch(L){if(O.aborted&&L===O.reason){if(W.signal?.aborted)throw new D(`"${X}" was cancelled.`,499);throw new D(`"${X}" did not finish within ${U} ms.`,504)}if(L instanceof D)throw L;if(L instanceof e||L instanceof v)throw new D(L.message);throw console.error(`[gemi/mcp] "${X}" failed for a remote caller:`,L),new D(`"${X}" failed on the server.`,500)}let k=(L)=>{return console.error(`[gemi/mcp] "${X}" ${L}`),new D(`"${X}" failed on the server.`,500)};if(U9(I))throw k("answered a job handle, and a remote call has no background jobs.");let A=I;if($.descriptor.outputSchema){let L=$.descriptor.outputSchema.safeParse(A);if(L.ok===!1)throw k(`answered what its output schema refuses: ${L.errors.join(", ")}`);A=L.value}if(A===void 0)return null;if(typeof A==="string")return A.length>T?`${A.slice(0,T)}\u2026 [cut at ${T} of ${A.length} characters]`:A;let M=JSON.stringify(A);if(M!==void 0&&M.length>T)return`${M.slice(0,T)}\u2026 [cut at ${T} of ${M.length} characters]`;return A}async fillPath($,Z){let{input:z,req:G}=Z,W=$.descriptor.url;for(let X of $.params){let Y=$.paramBinders.get(X.name),K=$.modelParams.get(X.name),Q;if(Y||K){if(Q=K?await this.bind($,`the param "${X.name}"`,()=>K.param.bind(z[K.key],G,Z),{refusals:!0}):await this.bind($,`the param "${X.name}"`,()=>Y(G,Z)),Q===void 0||Q===null||Q==="")throw console.error(`[gemi/mcp] The ${K?"resolver":"binder"} for "${X.name}" of "${$.descriptor.name}" returned ${JSON.stringify(Q)}.`),new D(`"${$.descriptor.name}" failed on the server.`,500)}else Q=z[X.name];let V=`:${X.name}${X.modifier}`;if(Q===void 0||Q===null||Q===""){if(X.modifier!=="?"&&X.modifier!=="*")throw new D(`"${X.name}" of "${$.descriptor.name}" must not be empty.`);W=W.replace(`/${V}`,"");continue}W=W.replace(V,m$($.descriptor.name,X,String(Q)))}return W}async formData($,Z,z){let{input:G,ctx:W}=z,X=$.descriptor.name;if(z.caller.kind==="remote"){let K=new FormData;for(let[Q,V]of Object.entries(Z))O3(K,Q,V);for(let Q of $.fileFields)K.append(Q.name,await this.remoteFile(X,Q.name,G[Q.name]));return K}if(!W)throw Error(`McpRegistry: "${X}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let Y=new FormData;for(let[K,Q]of Object.entries(Z))O3(Y,K,Q);for(let K of $.fileFields){let Q=K.binder==="input"?G[K.name]:await this.bind($,`the file "${K.name}"`,()=>K.binder(W,z));if(typeof Q!=="string"||Q==="")throw new D(`"${X}" needs a file for "${K.name}", and this turn has none. Ask the user to attach one.`);Y.append(K.name,await W.attachments.file(Q))}return Y}async remoteFile($,Z,z){let G=(H)=>new D(`"${Z}" of "${$}" ${H}`,400),W=z,{maxBytes:X,fetch:Y}=this.files;if(!E$(W.name))throw G("needs a name of 1 to 255 characters, without control characters or slashes.");if(!b$.test(W.mimeType))throw G(`has a mimeType that is not a media type: "${W.mimeType.slice(0,100)}".`);let K=typeof W.data==="string",Q=typeof W.url==="string";if(K===Q)throw G(Y?"needs exactly one of data or url.":"needs its bytes, base64, in data.");if(K){let H=W.data.replace(/\s+/g,"");if(Math.floor(H.length*3/4)-f$(H)>X)throw G(`is larger than the ${X} bytes a file may be.`);if(H.length%4!==0||!y$.test(H))throw G("has data that is not base64 (RFC 4648, standard alphabet, padded).");return new File([Buffer.from(H,"base64")],W.name,{type:W.mimeType})}if(!Y)throw G("cannot be fetched from a URL here. Send its bytes, base64, in data.");let V;try{V=new URL(W.url)}catch{throw G("has a url that is not a URL.")}if(V.protocol!=="https:")throw G("can only be fetched from an https URL.");try{let H=await H9(V.href,{...Y,maxSize:Math.min(Y.maxSize??X,X),redirect:"follow"});if(!H.ok)throw G(`could not be fetched: ${V.host} answered ${H.status}.`);let U=await H.arrayBuffer();return new File([U],W.name,{type:W.mimeType})}catch(H){if(H instanceof D)throw H;throw G(`could not be fetched from ${V.host}: ${H instanceof Error?H.message:"failed"}.`)}}grants($,Z){for(let z of $.scopes??[])if(this.scopeTools.get(z)?.tools.has(Z))return!0;return!1}resolveScopes(){let $=this.router.scopes;if($===void 0){this.scopeTools.set(x$,{description:"Use this app's tools as you",tools:new Set(this.plans.keys())});return}if(typeof $!=="object"||$===null)throw Error("McpRouter: scopes must be an object of scope names.");let Z=new Set([...this.plans.values()].flatMap(({descriptor:z})=>z.tags));for(let[z,G]of Object.entries($)){let W=`McpRouter: scope "${z}"`;if(!A$.test(z))throw Error(`${W} is not a valid OAuth scope: printable ASCII, no spaces or quotes.`);if(typeof G?.description!=="string"||G.description==="")throw Error(`${W} needs a description: it is what a consent screen shows.`);let X=new Set;for(let Y of G.tags??[]){if(!Z.has(Y))throw Error(`${W} names the tag "${Y}", which no tool has.`);for(let[K,{descriptor:Q}]of this.plans)if(Q.tags.includes(Y))X.add(K)}for(let Y of G.names??[]){if(!this.plans.has(Y))throw Error(`${W} names "${Y}", which is no tool.`);X.add(Y)}if(X.size===0)throw Error(`${W} reaches no tool. Give it tags or names.`);this.scopeTools.set(z,{description:G.description,tools:X})}}async project($,Z,z){let{name:G,outputSchema:W}=$.descriptor;if(z.status<200||z.status>=300)return await V9(G,z);let X=(U,F)=>{if(F===void 0)console.error(`[gemi/mcp] "${G}" ${U}`);else console.error(`[gemi/mcp] "${G}" ${U}`,F);return new D(`"${G}" failed on the server.`,500)},Y=z.headers.get("Content-Type"),K=await z.text(),Q=null;if(K!=="")try{Q=JSON.parse(K)}catch{throw X(`answered ${Y??"a body with no content-type"} that is not JSON, and its result or output needs JSON.`)}let V=Q;if($.result)try{V=await $.result(Q,Z)}catch(U){throw X("failed in its result projection:",U)}if(W){let U=W.safeParse(V);if(U.ok===!1)throw X(`answered what its output schema refuses: ${U.errors.join(", ")}`);V=U.value}if(V===void 0)return null;let H=JSON.stringify(V);if(H!==void 0&&H.length>T)return`${H.slice(0,T)}\u2026 [cut at ${T} of ${H.length} characters]`;return V}async bind($,Z,z,G={}){try{return await z()}catch(W){if(G.refusals&&W instanceof e)throw W;throw console.error(`[gemi/mcp] Binding ${Z} of "${$.descriptor.name}" failed:`,W),new D(`"${$.descriptor.name}" failed on the server.`,500)}}}function q$($){return $ instanceof Object&&$.__internal_brand==="McpTool"}function T$($){return typeof $==="object"&&$!==null&&typeof $.next==="function"&&Symbol.asyncIterator in $}async function $9($,Z,z){let G;if(z)G=new Promise((X,Y)=>{if(z.aborted)Y(z.reason);else z.addEventListener("abort",()=>Y(z.reason),{once:!0})}),G.catch(()=>{});let W=(X)=>G?Promise.race([X,G]):X;if(!T$($))return await W(Promise.resolve($));try{let X=0,Y=await W($.next());while(!Y.done)Z(Y.value,++X),Y=await W($.next());return Y.value}catch(X){throw $.return(void 0).catch(()=>{}),X}}function k$($,Z){if(typeof $==="string")return{progress:Z,message:$};if(typeof $==="object"&&$!==null){let{progress:z,total:G,message:W}=$;if(typeof z==="number"&&Number.isFinite(z))return{progress:z,...typeof G==="number"?{total:G}:{},...typeof W==="string"?{message:W}:{}};if(typeof W==="string")return{progress:Z,message:W}}return{progress:Z}}function Z9($,Z,z){let G=$.descriptor.name,W=`mcp_${crypto.randomUUID()}`,X=`mcp_call_${crypto.randomUUID()}`,Y=(Q)=>async()=>{throw new e(`"${G}" was called by a remote MCP client, which has no ${Q}.`,{retryable:!1})},K={put:Y("attachment store"),get:Y("attachment store"),read:Y("attachment store"),file:Y("attachment store")};return{context:Z,body:{},runId:W,toolCallId:X,signal:z,step:1,depth:0,resumed:!1,attachments:K,turn:Object.freeze({attachments:Object.freeze([])}),runAgent:Y("agent run to nest a sub-agent in"),generateImage:Y("attachment store to put an image in"),editImage:Y("attachment store to put an image in"),generate:(Q)=>N9({...Q,signal:Q.signal?AbortSignal.any([z,Q.signal]):z},{origin:{mcp:G,runId:W,toolCallId:X}}),jobs:J9({jobs:void 0,threadId:void 0,runId:W,toolCallId:X,toolName:G,depth:0,async:$.tool.async,attachments:null}),memory:F9()}}function W3($){if($.kind!=="tool"||$.meta.requiresApproval===!0)return;return $.tool.approvalPredicate}function z9($){return{context:$.context,body:$.body,runId:$.runId,threadId:$.threadId,toolCallId:$.toolCallId,step:$.step,depth:$.depth,signal:$.signal}}async function G9($,Z,z){try{return await $.tool.approvalPredicate(Z,z)!==!1}catch(G){return console.error(`[gemi/mcp] The requiresApproval of "${$.descriptor.name}" threw, so the call asks the user:`,G),!0}}function D3($){if($?.kind==="local"&&$.req)return;if($?.kind==="remote"&&$.req&&$.principal?.user&&typeof $.principal.id==="string"&&Array.isArray($.principal.scopes))return;throw Error('McpRegistry: a caller is { kind: "local", req } or { kind: "remote", req, principal }, with the principal a caller resolver verified.')}function W9($,Z){if(Z?.names&&!Z.names.includes($.name))return!1;if(Z?.tags&&!$.tags.some((z)=>Z.tags.includes(z)))return!1;return!0}var M$=10485760;function R$($){let Z=$?.maxBytes??M$;if(!Number.isSafeInteger(Z)||Z<=0)throw Error("McpRegistry: files.maxBytes must be a positive whole number of bytes.");let z=$?.fetchUrls===!0?{}:typeof $?.fetchUrls==="object"&&$.fetchUrls!==null?$.fetchUrls:null;return{maxBytes:Z,fetch:z}}function E$($){if($.length===0||$.length>255)return!1;for(let Z of $){let z=Z.codePointAt(0);if(z<32||z===127||Z==="/"||Z==="\\")return!1}return!0}var b$=/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}(\s*;.{0,200})?$/,y$=/^[A-Za-z0-9+/]*={0,2}$/;function f$($){return $.endsWith("==")?2:$.endsWith("=")?1:0}function X9($,Z,z){if(z!=="input"&&typeof z!=="function")throw Error(`${$}: ${Z} must be a function or "input".`)}function Y9($){return $ instanceof Object&&$.__internal_brand==="McpModelParam"}function w$($,Z,z){if(z.as!==void 0&&(typeof z.as!=="string"||z.as===""))throw Error(`${$}: ${Z}.as must be a non-empty string.`);let G=z.input;if(typeof G?.safeParse!=="function"||typeof G.toJSONSchema!=="function")throw Error(`${$}: ${Z}.input must be a schema, built with s.`);if(typeof z.bind!=="function")throw Error(`${$}: ${Z}.bind must be a function.`)}var v$=new Set(["readOnlyHint","destructiveHint","idempotentHint","openWorldHint"]);function g$($){switch($){case"GET":return{readOnlyHint:!0};case"DELETE":return{destructiveHint:!0};case"PUT":return{idempotentHint:!0};default:return{}}}function K9($,Z,z){if(z!==void 0&&(typeof z!=="object"||z===null))throw Error(`${$}: annotations must be an object of hints.`);let G=Z?g$(Z):{};for(let[W,X]of Object.entries(z??{})){if(!v$.has(W))throw Error(`${$}: "${W}" is not a tool annotation. Use readOnlyHint, destructiveHint, idempotentHint or openWorldHint.`);if(X===void 0)continue;if(typeof X!=="boolean")throw Error(`${$}: annotations.${W} must be true or false.`);G[W]=X}if(G.readOnlyHint===!0&&(G.destructiveHint||G.idempotentHint))throw Error(`${$} is read-only${z?.readOnlyHint?"":` (a ${Z})`} and also ${G.destructiveHint?"destructive":"idempotent"}, which only a tool that is not read-only can be. Set readOnlyHint: false if it changes something.`);return Object.freeze(G)}function Q9($,Z,z,G={}){let W=z.toJSONSchema(),X=Object.fromEntries(Object.entries(G).map(([Q,V])=>[Q,V.toJSONSchema()])),Y={...Z??{},type:"object",properties:{...Z?.properties??{},...W.properties,...X},required:[...Z?.required??[],...W.required??[],...Object.keys(G)],additionalProperties:!1},K=(Q)=>{if(typeof Q!=="object"||Q===null||Array.isArray(Q))return{ok:!1,errors:["expected an object"]};let V=[],H={};if($){let F=$.safeParse(Q);if(F.ok===!1)V.push(...F.errors);else H={...F.value}}let U=z.safeParse(Q);if(U.ok===!1)V.push(...U.errors);else H={...H,...U.value};for(let[F,O]of Object.entries(G)){let B=Q[F];if(B===void 0){V.push(`"${F}" is required`);continue}let I=O.safeParse(B);if(I.ok===!1)V.push(...I.errors.map((k)=>`${F}: ${k}`));else H[F]=I.value}return V.length>0?{ok:!1,errors:V}:{ok:!0,value:H}};return{toJSONSchema:()=>Y,safeParse:K,parse(Q){let V=K(Q);if(V.ok===!1)throw Error(V.errors.join(", "));return V.value}}}function h$($,Z){return Object.fromEntries(Object.entries($).filter(([z])=>!Z.includes(z)))}function m$($,Z,z){let G=Z.modifier==="*"||Z.modifier==="+"?z.split("/"):[z];if(G.some((W)=>W==="."||W===".."))throw new D(`"${z}" is not a valid value for "${Z.name}" of "${$}".`);return G.map(encodeURIComponent).join("/")}function O3($,Z,z){if(z===void 0||z===null)return;if(Array.isArray(z)){for(let G of z)O3($,Z,G);return}$.append(Z,typeof z==="object"?JSON.stringify(z):String(z))}function u$($){let Z=new URLSearchParams,z=(G,W)=>{if(W===void 0||W===null)return;if(Array.isArray(W)){for(let X of W)z(G,X);return}Z.append(G,typeof W==="object"?JSON.stringify(W):String(W))};for(let[G,W]of Object.entries($))z(G,W);return Z.toString()}async function V9($,Z){if(Z.status>=200&&Z.status<300){let G=Z.headers.get("Content-Type");if(G&&!S$.test(G)){let X=(await Z.arrayBuffer()).byteLength;return`"${$}" answered with ${X} bytes of ${G}, which is not shown.`}let W=await Z.text();if(W==="")return null;if(W.length>T)return`${W.slice(0,T)}\u2026 [cut at ${T} of ${W.length} characters]`;try{return JSON.parse(W)}catch{return W}}let z=await Z.text();if(Z.status>=400&&Z.status<500){let G=z.length>e3?`${z.slice(0,e3)}\u2026`:z;throw new D(`"${$}" was refused with ${Z.status}${G?`: ${G}`:"."}`,Z.status)}if(Z.status<500)console.error(`[gemi/mcp] "${$}" answered ${Z.status}${Z.headers.get("Location")?` to ${Z.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new D(`"${$}" failed on the server.`,Z.status)}import{createHash as d$,randomBytes as c$,timingSafeEqual as p$}from"crypto";function j3($){let Z=$.headers.get("authorization");if(!Z)return null;let z=/^Bearer[ ]+([\x21-\x7e]+)$/i.exec(Z.trim());return z?z[1]:null}class i${options;name="api-key";constructor($){this.options=$;if(typeof $?.prefix!=="string"||!/^[A-Za-z0-9_-]{3,32}$/.test($.prefix))throw Error('McpApiKeyResolver: prefix is required: 3 to 32 letters, digits, _ or -, like "myapp_mcp_".');if(typeof $.verify!=="function")throw Error("McpApiKeyResolver: verify must be a function that looks a key up.")}static generate($){return`${$}${c$(32).toString("base64url")}`}static hash($){return d$("sha256").update($).digest("hex")}static hashesMatch($,Z){let z=Buffer.from($),G=Buffer.from(Z);return z.length===G.length&&p$(z,G)}async resolve($){let Z=j3($);if(!Z?.startsWith(this.options.prefix))return null;let z=await this.options.verify(Z,$);if(!z)return{ok:!1,error:"invalid_token",description:"The API key is not valid."};if(!z.user||typeof z.id!=="string"||z.id===""||!Array.isArray(z.scopes))throw Error("McpApiKeyResolver: verify must answer { user, id, scopes } for a key it knows, or null.");return{ok:!0,principal:{user:z.user,via:this.name,id:z.id,scopes:[...z.scopes]}}}}import{createHash as R9,randomBytes as _9}from"crypto";async function x3($,Z){let z=l$();if(!z)return null;let G=await z.consume($,Z);return G.allowed?null:Math.max(1,Math.ceil(G.retryAfter/1000))}function l$(){try{let $=y();return $.bound(l)?$.make(l):null}catch{return null}}var A3=["2026-07-28"],Y3=["2025-11-25","2025-06-18","2025-03-26"],C3=[...A3,...Y3],S3="io.modelcontextprotocol/protocolVersion",q3="io.modelcontextprotocol/clientCapabilities";var T3="io.modelcontextprotocol/serverInfo",_={ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603,HeaderMismatch:-32020,MissingRequiredClientCapability:-32021,UnsupportedProtocolVersion:-32022};class P extends Error{code;data;status;constructor($,Z,z,G=200){super(Z);this.code=$;this.data=z;this.status=G;this.name="JsonRpcError"}}function R($,Z){return{jsonrpc:"2.0",id:$,error:{code:Z.code,message:Z.message,...Z.data!==void 0?{data:Z.data}:{}}}}function P9($){if(Array.isArray($))throw new P(_.InvalidRequest,"Batched requests are not supported.",void 0,400);if(typeof $!=="object"||$===null||$.jsonrpc!=="2.0")throw new P(_.InvalidRequest,"Not a JSON-RPC 2.0 message.",void 0,400);let Z=$,z=Z.id,G=typeof z==="string"||typeof z==="number"&&Number.isFinite(z);if(typeof Z.method==="string"){if(Z.params!==void 0&&(typeof Z.params!=="object"||Z.params===null||Array.isArray(Z.params)))throw new P(_.InvalidRequest,"params must be an object.",void 0,400);if(z===void 0)return{kind:"notification",message:Z};if(!G)throw new P(_.InvalidRequest,"id must be a string or a number.",void 0,400);return{kind:"request",message:Z}}if(G&&(("result"in Z)||("error"in Z)))return{kind:"response",message:Z};throw new P(_.InvalidRequest,"Not a JSON-RPC request, notification or response.",void 0,400)}function k3($,Z){let z=$.outputSchema?.toJSONSchema(),G={...$.annotations,...$.title&&!Z?{title:$.title}:{}};return{name:$.name,...$.title?{title:$.title}:{},description:$.description,inputSchema:$.inputSchema.toJSONSchema(),...z&&(Z||z.type==="object")?{outputSchema:z}:{},...Object.keys(G).length>0?{annotations:G}:{}}}function S($){return typeof $==="object"&&$!==null&&!Array.isArray($)}function I9($){let Z=/^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/.exec($);if(!Z)return $;try{let z=Buffer.from(Z[1],"base64");return new TextDecoder("utf-8",{fatal:!0}).decode(z)}catch{return null}}import{createHmac as n$,timingSafeEqual as t$}from"crypto";function M3($,Z,z){let G=Buffer.from(JSON.stringify(z)).toString("base64url");return`${Z}.${G}.${L9($,Z,G).toString("base64url")}`}function R3($,Z,z,G=Date.now()){if(typeof z!=="string"||z.length>8192)return null;let W=z.split(".");if(W.length!==3||W[0]!==Z)return null;let[,X,Y]=W,K=Buffer.from(Y,"base64url"),Q=L9($,Z,X);if(K.length!==Q.length||!t$(K,Q))return null;let V;try{V=JSON.parse(Buffer.from(X,"base64url").toString("utf8"))}catch{return null}if(typeof V!=="object"||V===null)return null;if(typeof V.exp!=="number"||V.exp<=G)return null;return V}function L9($,Z,z){return n$("sha256",K3($)).update(`${Z}.${z}`).digest()}var E3="gemi.mcp.request-state.v1",D9="gemi.mcp.session.v1",s$=86400000,O9=600000,j9="gemi_approval",b3=2000,o$={limit:600,window:60},a$={limit:30,window:60};class $3{registry;static token="router.mcp.http";endpoint;resolvers;context;allowedOrigins;rateLimit;maxBodyBytes;serverInfo;instructions;nonces;approvalTimeoutMs;feature;featureOn;pending=new Map;inflight=new Map;constructor($,Z,z={}){this.registry=$;if(typeof Z.url!=="string")throw Error(`route.mcp.remote.url is required: the endpoint's canonical URL, like "https://example.com/mcp".`);this.endpoint=$2(Z.url);let G=Z.resolvers??[];if(!Array.isArray(G)||G.length===0)throw Error("route.mcp.remote is enabled without a caller resolver. Configure route.mcp.remote.resolvers \u2014 an McpOAuthServer, an McpApiKeyResolver, or your own \u2014 or leave the endpoint off. Without one every request would be refused, or answered as nobody.");for(let W of G)if(typeof W?.resolve!=="function"||typeof W.name!=="string"||W.name==="")throw Error("route.mcp.remote.resolvers: each resolver needs a name and a resolve().");if(this.resolvers=G,K3(E3),this.context={resource:this.resource,registry:$},this.allowedOrigins=new Set((Z.allowedOrigins??[]).map((W)=>{if(new URL(W).origin!==W)throw Error(`route.mcp.remote.allowedOrigins: "${W}" is not an origin (scheme://host[:port]).`);return W})),this.allowedOrigins.add(this.endpoint.origin),this.rateLimit=Z.rateLimit===!1?!1:Z.rateLimit??o$,this.maxBodyBytes=Z.maxBodyBytes??Math.ceil($.maxFileBytes*1.4)+1048576,this.serverInfo={name:Z.server?.name??"gemi",version:Z.server?.version??"1.0.0"},this.instructions=Z.server?.instructions,this.nonces=Z.nonces??new k9,this.approvalTimeoutMs=Z.approvalTimeoutMs??O9,Z.feature!==void 0){if(typeof Z.feature!=="string"||Z.feature==="")throw Error("route.mcp.remote.feature must be a feature key from app/features.");this.feature=Z.feature}this.featureOn=z.featureOn??X3;for(let W of G)W.boot?.(this.context)}get resource(){return this.endpoint.href.replace(/\/$/,"")}async handle($){let Z=new URL($.url);if(Z.host!==this.endpoint.host)return null;if(this.feature!==void 0){if(!this.ownsPath(Z.pathname))return null;if(await this.featureOn(this.feature)!==!0)return null}for(let W of this.resolvers){let X=await W.handle?.($,this.context);if(X)return X}if(Z.pathname!==this.endpoint.pathname)return null;let z=$.headers.get("origin");if(z!==null&&!this.allowedOrigins.has(z))return f(R(null,new P(_.InvalidRequest,"Origin not allowed.")),403);let G=z?e$(z):{};if($.method==="OPTIONS")return new Response(null,{status:204,headers:{...G,"Access-Control-Allow-Methods":"POST","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name, Last-Event-ID","Access-Control-Max-Age":"600"}});if($.method!=="POST")return x9(new Response(null,{status:405,headers:{Allow:"POST, OPTIONS"}}),G);return x9(await this.post($),G)}ownsPath($){let Z=this.endpoint.pathname.replace(/\/$/,"");return $===this.endpoint.pathname||$===Z||$.startsWith(`${Z}/`)||$.startsWith("/.well-known/")}async post($){let Z=null;for(let Y of this.resolvers){let K=await Y.resolve($,this.context);if(K===null)continue;if(K.ok===!1)return await this.failedAttempt($)??this.unauthorized(K.description);Z={...K.principal,via:Y.name};break}if(!Z)return(j3($)!==null?await this.failedAttempt($):null)??this.unauthorized();let z=await this.spend(Z);if(z)return z;let G=$.headers.get("content-type")??"";if(!/^application\/json\s*(;|$)/i.test(G))return new Response("Content-Type must be application/json.",{status:415});let W;try{W=JSON.parse(await r$($,this.maxBodyBytes))}catch(Y){if(Y instanceof V3)return f(R(null,new P(_.InvalidRequest,`The request is larger than ${this.maxBodyBytes} bytes.`)),413);return f(R(null,new P(_.ParseError,"Parse error.")),400)}let X=new m($,{},"api",this.endpoint.pathname);return await g.run(X,async()=>{try{return await this.dispatch($,X,Z,W)}catch(Y){if(Y instanceof P){let K=S(W)&&(typeof W.id==="string"||typeof W.id==="number")?W.id:null;return f(R(K,Y),Y.status)}return console.error("[gemi/mcp] The MCP endpoint failed:",Y),f(R(null,new P(_.InternalError,"Internal error.")),500)}},{user:Z.user})}async dispatch($,Z,z,G){let W=P9(G),X={kind:"remote",req:Z,principal:z};if(W.kind==="response"){let B=this.session($,z),I=this.pending.get(String(W.message.id));if(I&&I.session===B.id)this.pending.delete(String(W.message.id)),I.resolve(W.message);return new Response(null,{status:202})}let{message:Y}=W,K=Y.params??{},Q=S(K._meta)?K._meta:{};if(typeof Q[S3]==="string"){if(this.checkModernHeaders($,Y,Q[S3]),W.kind==="notification")return new Response(null,{status:202});return await this.respond(Y,async(B,I)=>this.modern({principal:z,caller:X,message:Y,params:K,era:{modern:!0},stream:B,signal:I}),C9(K),$.signal)}if(Y.method==="initialize"){if(W.kind!=="request")throw new P(_.InvalidRequest,"initialize is a request.",void 0,400);return this.initialize(W.message,K,z)}let V=this.session($,z),H=$.headers.get("mcp-protocol-version");if(H!==null&&H!==V.state.v)throw new P(_.InvalidRequest,`MCP-Protocol-Version ${H} is not the session's ${V.state.v}.`,void 0,400);if(W.kind==="notification"){if(Y.method==="notifications/cancelled"){let B=K.requestId;this.inflight.get(`${V.id}:${String(B)}`)?.abort()}return new Response(null,{status:202})}let U=`${V.id}:${String(Y.id)}`,F=new AbortController;this.inflight.set(U,F);let O=AbortSignal.any([F.signal,$.signal]);try{return await this.respond(Y,async(B,I)=>this.legacy({principal:z,caller:X,message:Y,params:K,era:{modern:!1,session:V.state,sessionId:V.id},stream:B,signal:I}),C9(K)||z2(this.registry,Y,V.state),O,()=>this.inflight.delete(U))}catch(B){throw this.inflight.delete(U),B}}async modern($){switch($.message.method){case"server/discover":return this.complete({supportedVersions:C3,capabilities:{tools:{}},...this.instructions?{instructions:this.instructions}:{},ttlMs:60000,cacheScope:"private"});case"tools/list":return this.complete({tools:(await this.registry.available($.caller)).map((Z)=>k3(Z,!0)),ttlMs:60000,cacheScope:"private"});case"tools/call":return await this.callTool($);default:throw new P(_.MethodNotFound,`Method not found: ${$.message.method}`,void 0,404)}}async legacy($){switch($.message.method){case"ping":return{};case"tools/list":return{tools:(await this.registry.available($.caller)).map((Z)=>k3(Z,!1))};case"tools/call":return await this.callTool($);default:throw new P(_.MethodNotFound,`Method not found: ${$.message.method}`)}}complete($){return{resultType:"complete",...$,_meta:{[T3]:this.serverInfo}}}initialize($,Z,z){let G=Z.protocolVersion,W=Y3.includes(G)?G:Y3[0],X=S(Z.capabilities)?Z.capabilities:{},Y={v:W,e:A9(X.elicitation),p:Q3(z),exp:Date.now()+s$},K=M3(D9,"gms1",Y);return f({jsonrpc:"2.0",id:$.id,result:{protocolVersion:W,capabilities:{tools:{listChanged:!1}},serverInfo:this.serverInfo,...this.instructions?{instructions:this.instructions}:{}}},200,{"Mcp-Session-Id":K})}session($,Z){let z=$.headers.get("mcp-session-id");if(!z)throw new P(_.InvalidRequest,"Missing Mcp-Session-Id. Send initialize first, or a request carrying io.modelcontextprotocol/protocolVersion in _meta.",void 0,400);let G=R3(D9,"gms1",z);if(!G||G.p!==Q3(Z))throw new P(_.InvalidRequest,"Session not found.",void 0,404);return{id:z,state:G}}checkModernHeaders($,Z,z){if(!A3.includes(z))throw new P(_.UnsupportedProtocolVersion,"Unsupported protocol version",{supported:C3,requested:z},400);let G=(Y)=>new P(_.HeaderMismatch,`Header mismatch: ${Y}`,void 0,400);if($.headers.get("mcp-protocol-version")!==z)throw G("MCP-Protocol-Version does not match the protocol version in _meta.");if($.headers.get("mcp-method")!==Z.method)throw G("Mcp-Method does not match the method.");let W=Z.params??{},X=Z.method==="tools/call"||Z.method==="prompts/get"?W.name:Z.method==="resources/read"?W.uri:void 0;if(X!==void 0){let Y=$.headers.get("mcp-name");if(Y===null||I9(Y)!==X)throw G("Mcp-Name does not match the request's name.")}}async callTool($){let{params:Z,caller:z,principal:G}=$,W=Z.name;if(typeof W!=="string")throw new P(_.InvalidParams,"tools/call needs a tool name.");let X=Z.arguments??{},Y=(await this.registry.available(z,{names:[W]}))[0];if(!Y)throw new P(_.InvalidParams,`Unknown tool: ${W}`);let K=!1;if(Y.requiresApproval){let F=Y.inputSchema.safeParse(X);if(F.ok===!1)return this.toolResult($,{content:[{type:"text",text:`Invalid arguments for "${W}": ${F.errors.join(", ")}`}],isError:!0});if($.era.modern&&S($.params.inputResponses)&&$.params.requestState!==void 0||await this.registry.approvalRequired(z,W,X,{signal:$.signal})){let B=await this.approval($,Y,X);if(B.kind==="answer")return B.result;K=!0}}let Q=G2($),V;try{V=await this.registry.execute(z,W,X,void 0,{approved:K,signal:$.signal,...Q?{progress:Q}:{}})}catch(F){if(F instanceof D)return this.toolResult($,{content:[{type:"text",text:F.message}],isError:!0});if(F instanceof v)throw new P(_.InvalidParams,F.reason==="unknown-tool"?`Unknown tool: ${W}`:F.message);return console.error(`[gemi/mcp] "${W}" failed for ${G.via}:${G.id}:`,F),this.toolResult($,{content:[{type:"text",text:`"${W}" failed on the server.`}],isError:!0})}let H=typeof V==="string"?V:JSON.stringify(V??null),U=Y.outputSchema&&($.era.modern||S(V))?{structuredContent:V}:{};return this.toolResult($,{content:[{type:"text",text:H}],...U,isError:!1})}toolResult($,Z){return $.era.modern?this.complete(Z):Z}async approval($,Z,z){let G=W2(Z,z),W=(V)=>({kind:"answer",result:this.toolResult($,{content:[{type:"text",text:V}],isError:!0})});if($.era.modern){let V=S($.params._meta)?$.params._meta:{},H=S(V[q3])?V[q3]:{},U=S($.params.inputResponses)?$.params.inputResponses:null,F=$.params.requestState,O=Z2(z);if(U&&F!==void 0){let I=R3(E3,"grs1",F);if(!I||I.p!==Q3($.principal)||I.t!==Z.name||I.a!==O)throw new P(_.InvalidParams,"requestState is not valid for this call.");if(!await this.spendNonce(I.n,I.exp))throw new P(_.InvalidParams,"requestState has already been used.");return S9(U[j9])?{kind:"approved"}:W(`The user did not approve "${Z.name}", and it did not run.`)}if(!A9(H.elicitation))throw new P(_.MissingRequiredClientCapability,`"${Z.name}" needs the user's approval, and this client cannot ask for it (elicitation).`,{requiredCapabilities:{elicitation:{form:{}}}},400);let B={p:Q3($.principal),t:Z.name,a:O,n:_9(16).toString("base64url"),exp:Date.now()+O9};return{kind:"answer",result:{resultType:"input_required",inputRequests:{[j9]:{method:"elicitation/create",params:{mode:"form",...G}}},requestState:M3(E3,"grs1",B),_meta:{[T3]:this.serverInfo}}}}let X=$.era;if(!X.session.e||!$.stream)return W(`"${Z.name}" needs the user's approval, and this client cannot ask for it (elicitation). It did not run.`);let Y=`gemi-approval-${_9(12).toString("base64url")}`,K=X.sessionId,Q=await new Promise((V)=>{let H=setTimeout(()=>{this.pending.delete(Y),V(null)},this.approvalTimeoutMs),U=(F)=>{clearTimeout(H),V(F)};this.pending.set(Y,{session:K,resolve:U}),$.signal.addEventListener("abort",()=>{this.pending.delete(Y),U(null)}),$.stream.send({jsonrpc:"2.0",id:Y,method:"elicitation/create",params:X.session.v==="2025-11-25"?{mode:"form",...G}:G})});return Q&&!Q.error&&S9(Q.result)?{kind:"approved"}:W(`The user did not approve "${Z.name}", and it did not run.`)}async spendNonce($,Z){try{return await this.nonces.consume($,Z)}catch(z){return console.error("[gemi/mcp] Spending an approval failed:",z),!1}}async respond($,Z,z,G,W){if(!z)try{let Q=await Z(null,G);return f({jsonrpc:"2.0",id:$.id,result:Q},200)}finally{W?.()}let X=new AbortController,Y=AbortSignal.any([X.signal,G]),K=new E9(()=>X.abort());return(async()=>{try{let Q=await Z(K,Y);K.send({jsonrpc:"2.0",id:$.id,result:Q})}catch(Q){if(Q instanceof P)K.send(R($.id,Q));else console.error("[gemi/mcp] The MCP endpoint failed:",Q),K.send(R($.id,new P(_.InternalError,"Internal error.")))}finally{W?.(),K.close()}})(),new Response(K.readable,{status:200,headers:{"Content-Type":"text/event-stream","Cache-Control":"no-store","X-Accel-Buffering":"no"}})}unauthorized($){let Z=this.resolvers.map((G)=>G.challenge?.(this.context)).find(Boolean)??'Bearer realm="mcp"',z=$?`${Z}, error="invalid_token", error_description="${$.replace(/["\\]/g,"")}"`:Z;return f(R(null,new P(_.InvalidRequest,"Unauthorized.")),401,{"WWW-Authenticate":z})}async failedAttempt($){let Z=T9(new m($,{},"api",this.endpoint.pathname)),z=await x3(`mcp-auth-failed:${Z}`,a$);return z===null?null:q9(z)}async spend($){if(!this.rateLimit)return null;let Z=await x3(`mcp-endpoint:${$.via}:${$.id}`,this.rateLimit);return Z===null?null:q9(Z)}}class E9{readable;controller;closed=!1;encoder=new TextEncoder;constructor($){this.readable=new ReadableStream({start:(Z)=>{this.controller=Z},cancel:()=>{this.closed=!0,$()}})}send($){if(this.closed)return;this.controller.enqueue(this.encoder.encode(`event: message
|
|
2
|
+
import{$b as M9,Jc as F9,Xb as k9,_b as K3,dc as e,ld as N9,qc as U9,wc as J9}from"./chunk-7dwgex81.js";import{Bd as I$,sd as c3,ud as B3,xd as d9,yd as c9,zd as Q$}from"./chunk-r1x5ms0y.js";import{Ed as w9,Jd as v9,Ld as g9,Sd as x,Td as h9,Ud as m9,Wd as u9,Xd as N$}from"./chunk-6zz3kez5.js";import{_d as N}from"./chunk-6hs102jh.js";import{Bf as X$,Cf as z$,Cg as W$,Df as G$,Dg as Y$,Ee as V$,Fe as p3,Ff as w,Je as J3,Jg as _$,Ke as X3,Sf as a3,bg as s3,cf as m3,dg as n,hg as o3,ig as t9,jg as s9,ke as l,ne as T9,pg as f3,qf as Z3,qg as o9,re as D$,rg as G3,sg as r9,tg as e9,ue as K$,ug as w3,vg as a,wf as H3,wg as L3,xf as h3,xg as d3,yf as q}from"./chunk-akahs8fn.js";import{Oh as p,Qh as m,di as t3,ri as H9,vi as P3,wi as F3,xi as z3}from"./chunk-eve0hewa.js";import{Di as i9}from"./chunk-v2cv3wcw.js";import{Oi as g}from"./chunk-7hdfbp2x.js";import{Ui as u3}from"./chunk-dzvp8ynr.js";import{$i as N3,Xi as H$,Yi as i3,aj as n3,dj as J$}from"./chunk-23mcs80p.js";import{rj as l9,wj as n9}from"./chunk-e9xbdgb2.js";import{Mj as f9,Oj as u}from"./chunk-98dxtkbt.js";import{Pj as j}from"./chunk-ks7msv3e.js";import{Vj as P$}from"./chunk-yxjfghq3.js";import{Jk as l3}from"./chunk-t7qp951h.js";import{Qk as U3,Tk as y}from"./chunk-kddgfct7.js";import{al as g3}from"./chunk-eyqsvzej.js";import{ll as a9}from"./chunk-5aj27bgc.js";var x$="mcp",A$=/^[\x21\x23-\x5B\x5D-\x7E]+$/;class D extends e{status;constructor($,Z){super($);this.status=Z;this.name="McpToolError"}}class v extends Error{reason;constructor($,Z){super($);this.reason=Z;this.name="McpCallRefusedError"}}var r3=/^[A-Za-z0-9_-]{1,64}$/,C$=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,e3=4000,T=1e5,S$=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i,B9=new WeakMap;function G6($){return B9.get($)}class i{router;dispatcher;static token="router.mcp";plans=new Map;scopeTools=new Map;files;featureOn;constructor($,Z,z={}){this.router=$;this.dispatcher=Z;this.files=R$(z.files),this.featureOn=z.featureOn??X3;for(let[G,W]of Object.entries($.routes??{}))this.plans.set(G,q$(W)?this.planNative(G,W):this.plan(G,W));this.resolveScopes()}list($,Z){if(D3($),$.kind==="local")return this.descriptors(Z);let z=[];for(let[G,W]of this.plans){if(!W.remote||!this.grants($.principal,G))continue;if(W9(W.remote,Z))z.push(W.remote)}return z}async available($,Z){let z=[];for(let G of this.list($,Z))if(await this.isOn(G))z.push(G);return z}descriptors($){let Z=[];for(let{descriptor:z}of this.plans.values())if(W9(z,$))Z.push(z);return Z}get maxFileBytes(){return this.files.maxBytes}scopes(){return[...this.scopeTools].map(([$,{description:Z}])=>({name:$,description:Z}))}scopesFor($){return[...this.scopeTools].filter(([,{tools:Z}])=>Z.has($)).map(([Z])=>Z)}async execute($,Z,z,G,W={}){D3($);let X=this.plans.get(Z);if(X&&!await this.isOn(X.descriptor)){if($.kind==="remote")throw new v(`There is no tool named "${Z}".`,"unknown-tool");throw new D(`There is no tool named "${Z}".`,404)}if($.kind==="remote"){if(!X?.remote||!this.grants($.principal,Z))throw new v(`There is no tool named "${Z}".`,"unknown-tool");if(X.descriptor.requiresApproval&&W.approved!==!0&&!W3(X))throw new v(`"${Z}" needs the user's approval, and this call does not have it.`,"approval-required");G=void 0}if(!X)throw Error(`McpRegistry: there is no tool named "${Z}".`);if(X.kind==="tool")return await this.executeNative(X,$,z,G,W);let Y=X,K=$.kind==="remote"?Y.remote:Y.descriptor,Q=K.inputSchema.safeParse(z);if(Q.ok===!1)throw new D(`Invalid arguments for "${Z}": ${Q.errors.join(", ")}`);let V=Q.value,H={caller:$,req:$.req,tool:K,input:V,ctx:G},U=await this.fillPath(Y,H),{path:F}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${U}`));if(F!==K.url)throw new D(`"${Z}" has nothing at ${U}.`,404);let O={};for(let L of Y.jsonKeys)if(V[L]!==void 0)O[L]=V[L];let B,I="";if(Y.fileFields.length>0)B=await this.formData(Y,O,H);else if(K.method==="GET")I=u$(O);else if(Y.jsonKeys.length>0)B=O;let k=this.router.credentials?await this.bind(Y,"the credentials",()=>this.router.credentials(H)):void 0,A={...k?{credentials:k}:{},...W.signal?{signal:W.signal}:{},...W.progress?{progress:W.progress}:{},...$.kind==="remote"?{identity:{user:$.principal.user,grant:{via:$.principal.via,id:$.principal.id,scopes:$.principal.scopes,...$.principal.clientId?{clientId:$.principal.clientId}:{}}}}:{}},M;try{let L=I?`${U}?${I}`:U;M=Object.keys(A).length>0?await this.dispatcher.dispatchAs($.req,K.method,L,B,A):await this.dispatcher.dispatchAs($.req,K.method,L,B)}catch(L){throw console.error(`[gemi/mcp] Dispatching "${Z}" failed:`,L),new D(`"${Z}" failed on the server.`,500)}if(!Y.result&&!K.outputSchema)return await V9(Z,M);return await this.project(Y,H,M)}async isOn($){return $.feature===void 0||await this.featureOn($.feature)===!0}plan($,Z){if(!r3.test($))throw Error(`McpRouter: "${$}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let z=`McpRouter: "${$}" (${Z?.method} ${Z?.url})`;if(!(Z instanceof Object)||Z.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${$}" is not a route or a tool. Declare it with this.fromApiRoute(...) or this.fromTool(...).`);let{method:G,url:W,meta:X}=Z;if(X.feature!==void 0&&(typeof X.feature!=="string"||X.feature===""))throw Error(`${z}: feature must be a feature key from app/features.`);if(W.startsWith("/__gemi__"))throw Error(`${z} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[W]?.[G];if(!Y)throw Error(`${z} names no route of this app. Expose a route by the path and verb it is mounted at.`);let K=[...W.matchAll(C$)].map(([,J,C])=>({name:J,modifier:C})),Q=X.params??{};for(let J of K)if(!(J.name in Q))throw Error(`${z}: the path param "${J.name}" must be bound or declared "input".`);for(let[J,C]of Object.entries(Q)){if(!K.some((h)=>h.name===J))throw Error(`${z}: "${J}" in params is not a param of the url.`);if(Y9(C))w$(z,`params.${J}`,C);else X9(z,`params.${J}`,C)}let V=[],H;if(X.input){if(H=X.input.toJSONSchema(),H.type!=="object"||!H.properties)throw Error(`${z}: input must be an s.object(...), the fields of the request body.`);V=Object.keys(H.properties)}let U=Object.entries(X.files??{}).map(([J,C])=>({name:J,binder:C}));for(let J of U)X9(z,`files.${J.name}`,J.binder);if(X.result!==void 0&&typeof X.result!=="function")throw Error(`${z}: result must be a function.`);if(X.output!==void 0&&typeof X.output?.safeParse!=="function")throw Error(`${z}: output must be a schema, built with s.`);if(X.title!==void 0&&(typeof X.title!=="string"||X.title===""))throw Error(`${z}: title must be a non-empty string.`);let F=K9(z,G,X.annotations);if(U.length>0&&G==="GET")throw Error(`${z}: a GET has no body to carry a file.`);let O={},B=(J,C)=>{if(J in O)throw Error(`${z}: "${J}" is the input key of two params or files, and the model can only send one.`);O[J]=C},I=new Map,k=new Map;for(let J of K){let C=Q[J.name];if(C==="input"){let h=p.string().describe(`The ":${J.name}" segment of the url.`);B(J.name,J.modifier==="?"||J.modifier==="*"?h.optional():h)}else if(Y9(C)){let h=C.as??J.name;B(h,C.input),k.set(J.name,{key:h,param:C})}else I.set(J.name,C)}let A={};for(let J of U)if(J.binder==="input")B(J.name,p.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field.")),A[J.name]=this.remoteFileSchema();let M=[...Object.keys(O),...U.map((J)=>J.name)];for(let J of new Set(M))if(V.includes(J))throw Error(`${z}: "${J}" is both an input field and a param or file, and the model can only send one.`);let L=Object.freeze({kind:"route",name:$,description:X.description,method:G,url:W,inputSchema:Q9(X.input,H,p.object(O)),...X.output?{outputSchema:X.output}:{},...X.title?{title:X.title}:{},annotations:F,tags:Object.freeze([...X.tags??[]]),requiresApproval:X.requiresApproval===!0,...X.feature!==void 0?{feature:X.feature}:{},...Y.source?{source:Y.source}:{}}),r=!U.every((J)=>J.binder==="input")?null:U.length===0?L:Object.freeze({...L,inputSchema:Q9(X.input,H,p.object(h$(O,Object.keys(A))),A)});return{kind:"route",descriptor:L,remote:r,params:K,paramBinders:I,modelParams:k,fileFields:U,jsonKeys:V,...X.result?{result:X.result}:{}}}remoteFileSchema(){let{maxBytes:$,fetch:Z}=this.files,z={name:{type:"string",description:"The file's name, like report.pdf."},mimeType:{type:"string",description:"The file's media type, like application/pdf."},data:{type:"string",contentEncoding:"base64",description:`The file's bytes, base64-encoded (RFC 4648, standard alphabet). At most ${$} bytes decoded.`}};if(Z)z.url={type:"string",format:"uri",description:"An https URL the server downloads the file from, instead of data."};let G={type:"object",description:Z?"A file, sent as base64 in data or fetched from url: exactly one of the two.":"A file, sent as base64 in data.",properties:z,required:["name","mimeType"],additionalProperties:!1},W=Object.keys(z),X=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["a file must be an object"]};let K=[],Q={};for(let V of Object.keys(Y))if(!W.includes(V))K.push(`a file has no field "${V}"`);for(let V of W){let H=Y[V];if(H===void 0||H===null){if(V==="name"||V==="mimeType")K.push(`a file needs "${V}"`);continue}if(typeof H!=="string")K.push(`a file's "${V}" must be a string`);else Q[V]=H}return K.length>0?{ok:!1,errors:K}:{ok:!0,value:Q}};return{toJSONSchema:()=>G,safeParse:X,parse(Y){let K=X(Y);if(K.ok===!1)throw Error(K.errors.join(", "));return K.value}}}planNative($,Z){if(!r3.test($))throw Error(`McpRouter: "${$}" is not a valid tool name. A routes key is the tool's name as a model and an MCP client see it, used verbatim \u2014 letters, digits, "_" and "-", at most 64.`);let z=`McpRouter: "${$}" (a native tool)`,{tool:G,meta:W={}}=Z;if(!(G instanceof Object)||typeof G.name!=="string")throw Error(`${z} is not an AgentTool. Pass one built with AgentTool.create(...).`);if(G.answeredBy==="client"||typeof G.execute!=="function")throw Error(`${z}: "${G.name}" is answered by the client, and only a tool the server runs can be called through the registry.`);if(typeof G.inputSchema==="function")throw Error(`${z}: "${G.name}" resolves its input schema per run, and the registry lists one schema to every caller. Give it a fixed inputSchema.`);if(typeof G.inputSchema?.safeParse!=="function")throw Error(`${z}: its inputSchema must be a schema, built with s.`);if(typeof W!=="object"||W===null)throw Error(`${z}: the meta must be an object.`);if(W.description!==void 0&&(typeof W.description!=="string"||W.description.trim()===""))throw Error(`${z}: description must be a non-empty string.`);if(W.title!==void 0&&(typeof W.title!=="string"||W.title===""))throw Error(`${z}: title must be a non-empty string.`);if(W.tags!==void 0&&(!Array.isArray(W.tags)||W.tags.some((Q)=>typeof Q!=="string")))throw Error(`${z}: tags must be an array of strings.`);if(W.requiresApproval===!1&&G.requiresApproval)throw Error(`${z}: "${G.name}" requires approval, and requiresApproval: false would run it without asking. Leave it out, or register a tool that does not ask.`);let X=W.remote;if(X!==void 0){if(typeof X!=="object"||X===null||typeof X.context!=="function")throw Error(`${z}: remote must be { context(call) }, building the AgentContext a remote call runs with.`);if(X.progress!==void 0&&typeof X.progress!=="function")throw Error(`${z}: remote.progress must be a function.`)}let Y=W.description??G.description;if(typeof Y!=="string"||Y.trim()==="")throw Error(`${z} needs a description: the tool's own, or the meta's.`);let K=Object.freeze({kind:"tool",name:$,description:Y,inputSchema:G.inputSchema,...G.outputSchema?{outputSchema:G.outputSchema}:{},...W.title?{title:W.title}:{},annotations:K9(z,void 0,W.annotations),tags:Object.freeze([...W.tags??[]]),requiresApproval:G.requiresApproval||W.requiresApproval===!0});return{kind:"tool",descriptor:K,remote:X?K:null,tool:G,meta:W}}async approvalRequired($,Z,z,G={}){let W=this.plans.get(Z);if(!W)return!0;if(!W3(W))return W.descriptor.requiresApproval;let X=W;if($.kind!=="remote"||!X.meta.remote)return!0;let Y=X.descriptor.inputSchema.safeParse(z);if(Y.ok===!1)return!0;let K={caller:$,req:$.req,tool:X.descriptor,input:Y.value},Q;try{Q=await this.bind(X,"the remote context",()=>X.meta.remote.context(K))}catch{return!0}if(typeof Q!=="object"||Q===null)return!0;let V=G.signal??new AbortController().signal;return G9(X,Y.value,z9(Z9(X,Q,V)))}nativeApproval($){let Z=this.plans.get($);if(Z?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${$}".`);return W3(Z)??Z.descriptor.requiresApproval}nativeTool($){let Z=this.plans.get($);if(Z?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${$}".`);return Z.tool}startNative($,Z,z,G){let W=this.plans.get(Z);if(W?.kind!=="tool")throw Error(`McpRegistry: there is no native tool named "${Z}".`);if($===void 0)return W.tool.execute(z,G);if(D3($),$.kind!=="local")throw Error("McpRegistry: startNative is for a local caller. A remote call goes through execute().");let X=W.descriptor.inputSchema.safeParse(z);if(X.ok===!1)throw new D(`Invalid arguments for "${Z}": ${X.errors.join(", ")}`);return this.runNative(W,{caller:$,req:$.req,tool:W.descriptor,input:X.value},G)}runNative($,Z,z){let G={...z};return B9.set(G,{...Z,ctx:G}),$.tool.execute(Z.input,G)}async executeNative($,Z,z,G,W){let{name:X}=$.descriptor,Y=$.descriptor.inputSchema.safeParse(z);if(Y.ok===!1)throw new D(`Invalid arguments for "${X}": ${Y.errors.join(", ")}`);let K={caller:Z,req:Z.req,tool:$.descriptor,input:Y.value},Q=(L,_3)=>{if(!W.progress)return;let r;try{r=$.meta.remote?.progress?$.meta.remote.progress(L,_3):k$(L,_3)}catch(J){console.error(`[gemi/mcp] The progress mapping of "${X}" failed:`,J);return}if(r)W.progress(r)};if(Z.kind==="local"){if(!G)throw Error(`McpRegistry: "${X}" is a native tool, which runs with the tool context of an agent call, and none was passed to execute().`);return await $9(this.runNative($,{...K,ctx:G},G),Q)}let V=$.meta.remote,H=await this.bind($,"the remote context",()=>V.context(K));if(typeof H!=="object"||H===null)throw console.error(`[gemi/mcp] The remote context of "${X}" is not an object:`,H),new D(`"${X}" failed on the server.`,500);let U=$.tool.timeoutMs,F=[...W.signal?[W.signal]:[],...U!==void 0?[AbortSignal.timeout(U)]:[]],O=F.length===0?new AbortController().signal:F.length===1?F[0]:AbortSignal.any(F),B=Z9($,H,O);if(!W.approved&&W3($)&&await G9($,K.input,z9(B)))throw new v(`"${X}" needs the user's approval, and this call does not have it.`,"approval-required");let I;try{I=await $9(this.runNative($,{...K,ctx:B},B),Q,O)}catch(L){if(O.aborted&&L===O.reason){if(W.signal?.aborted)throw new D(`"${X}" was cancelled.`,499);throw new D(`"${X}" did not finish within ${U} ms.`,504)}if(L instanceof D)throw L;if(L instanceof e||L instanceof v)throw new D(L.message);throw console.error(`[gemi/mcp] "${X}" failed for a remote caller:`,L),new D(`"${X}" failed on the server.`,500)}let k=(L)=>{return console.error(`[gemi/mcp] "${X}" ${L}`),new D(`"${X}" failed on the server.`,500)};if(U9(I))throw k("answered a job handle, and a remote call has no background jobs.");let A=I;if($.descriptor.outputSchema){let L=$.descriptor.outputSchema.safeParse(A);if(L.ok===!1)throw k(`answered what its output schema refuses: ${L.errors.join(", ")}`);A=L.value}if(A===void 0)return null;if(typeof A==="string")return A.length>T?`${A.slice(0,T)}\u2026 [cut at ${T} of ${A.length} characters]`:A;let M=JSON.stringify(A);if(M!==void 0&&M.length>T)return`${M.slice(0,T)}\u2026 [cut at ${T} of ${M.length} characters]`;return A}async fillPath($,Z){let{input:z,req:G}=Z,W=$.descriptor.url;for(let X of $.params){let Y=$.paramBinders.get(X.name),K=$.modelParams.get(X.name),Q;if(Y||K){if(Q=K?await this.bind($,`the param "${X.name}"`,()=>K.param.bind(z[K.key],G,Z),{refusals:!0}):await this.bind($,`the param "${X.name}"`,()=>Y(G,Z)),Q===void 0||Q===null||Q==="")throw console.error(`[gemi/mcp] The ${K?"resolver":"binder"} for "${X.name}" of "${$.descriptor.name}" returned ${JSON.stringify(Q)}.`),new D(`"${$.descriptor.name}" failed on the server.`,500)}else Q=z[X.name];let V=`:${X.name}${X.modifier}`;if(Q===void 0||Q===null||Q===""){if(X.modifier!=="?"&&X.modifier!=="*")throw new D(`"${X.name}" of "${$.descriptor.name}" must not be empty.`);W=W.replace(`/${V}`,"");continue}W=W.replace(V,m$($.descriptor.name,X,String(Q)))}return W}async formData($,Z,z){let{input:G,ctx:W}=z,X=$.descriptor.name;if(z.caller.kind==="remote"){let K=new FormData;for(let[Q,V]of Object.entries(Z))O3(K,Q,V);for(let Q of $.fileFields)K.append(Q.name,await this.remoteFile(X,Q.name,G[Q.name]));return K}if(!W)throw Error(`McpRegistry: "${X}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let Y=new FormData;for(let[K,Q]of Object.entries(Z))O3(Y,K,Q);for(let K of $.fileFields){let Q=K.binder==="input"?G[K.name]:await this.bind($,`the file "${K.name}"`,()=>K.binder(W,z));if(typeof Q!=="string"||Q==="")throw new D(`"${X}" needs a file for "${K.name}", and this turn has none. Ask the user to attach one.`);Y.append(K.name,await W.attachments.file(Q))}return Y}async remoteFile($,Z,z){let G=(H)=>new D(`"${Z}" of "${$}" ${H}`,400),W=z,{maxBytes:X,fetch:Y}=this.files;if(!E$(W.name))throw G("needs a name of 1 to 255 characters, without control characters or slashes.");if(!b$.test(W.mimeType))throw G(`has a mimeType that is not a media type: "${W.mimeType.slice(0,100)}".`);let K=typeof W.data==="string",Q=typeof W.url==="string";if(K===Q)throw G(Y?"needs exactly one of data or url.":"needs its bytes, base64, in data.");if(K){let H=W.data.replace(/\s+/g,"");if(Math.floor(H.length*3/4)-f$(H)>X)throw G(`is larger than the ${X} bytes a file may be.`);if(H.length%4!==0||!y$.test(H))throw G("has data that is not base64 (RFC 4648, standard alphabet, padded).");return new File([Buffer.from(H,"base64")],W.name,{type:W.mimeType})}if(!Y)throw G("cannot be fetched from a URL here. Send its bytes, base64, in data.");let V;try{V=new URL(W.url)}catch{throw G("has a url that is not a URL.")}if(V.protocol!=="https:")throw G("can only be fetched from an https URL.");try{let H=await H9(V.href,{...Y,maxSize:Math.min(Y.maxSize??X,X),redirect:"follow"});if(!H.ok)throw G(`could not be fetched: ${V.host} answered ${H.status}.`);let U=await H.arrayBuffer();return new File([U],W.name,{type:W.mimeType})}catch(H){if(H instanceof D)throw H;throw G(`could not be fetched from ${V.host}: ${H instanceof Error?H.message:"failed"}.`)}}grants($,Z){for(let z of $.scopes??[])if(this.scopeTools.get(z)?.tools.has(Z))return!0;return!1}resolveScopes(){let $=this.router.scopes;if($===void 0){this.scopeTools.set(x$,{description:"Use this app's tools as you",tools:new Set(this.plans.keys())});return}if(typeof $!=="object"||$===null)throw Error("McpRouter: scopes must be an object of scope names.");let Z=new Set([...this.plans.values()].flatMap(({descriptor:z})=>z.tags));for(let[z,G]of Object.entries($)){let W=`McpRouter: scope "${z}"`;if(!A$.test(z))throw Error(`${W} is not a valid OAuth scope: printable ASCII, no spaces or quotes.`);if(typeof G?.description!=="string"||G.description==="")throw Error(`${W} needs a description: it is what a consent screen shows.`);let X=new Set;for(let Y of G.tags??[]){if(!Z.has(Y))throw Error(`${W} names the tag "${Y}", which no tool has.`);for(let[K,{descriptor:Q}]of this.plans)if(Q.tags.includes(Y))X.add(K)}for(let Y of G.names??[]){if(!this.plans.has(Y))throw Error(`${W} names "${Y}", which is no tool.`);X.add(Y)}if(X.size===0)throw Error(`${W} reaches no tool. Give it tags or names.`);this.scopeTools.set(z,{description:G.description,tools:X})}}async project($,Z,z){let{name:G,outputSchema:W}=$.descriptor;if(z.status<200||z.status>=300)return await V9(G,z);let X=(U,F)=>{if(F===void 0)console.error(`[gemi/mcp] "${G}" ${U}`);else console.error(`[gemi/mcp] "${G}" ${U}`,F);return new D(`"${G}" failed on the server.`,500)},Y=z.headers.get("Content-Type"),K=await z.text(),Q=null;if(K!=="")try{Q=JSON.parse(K)}catch{throw X(`answered ${Y??"a body with no content-type"} that is not JSON, and its result or output needs JSON.`)}let V=Q;if($.result)try{V=await $.result(Q,Z)}catch(U){throw X("failed in its result projection:",U)}if(W){let U=W.safeParse(V);if(U.ok===!1)throw X(`answered what its output schema refuses: ${U.errors.join(", ")}`);V=U.value}if(V===void 0)return null;let H=JSON.stringify(V);if(H!==void 0&&H.length>T)return`${H.slice(0,T)}\u2026 [cut at ${T} of ${H.length} characters]`;return V}async bind($,Z,z,G={}){try{return await z()}catch(W){if(G.refusals&&W instanceof e)throw W;throw console.error(`[gemi/mcp] Binding ${Z} of "${$.descriptor.name}" failed:`,W),new D(`"${$.descriptor.name}" failed on the server.`,500)}}}function q$($){return $ instanceof Object&&$.__internal_brand==="McpTool"}function T$($){return typeof $==="object"&&$!==null&&typeof $.next==="function"&&Symbol.asyncIterator in $}async function $9($,Z,z){let G;if(z)G=new Promise((X,Y)=>{if(z.aborted)Y(z.reason);else z.addEventListener("abort",()=>Y(z.reason),{once:!0})}),G.catch(()=>{});let W=(X)=>G?Promise.race([X,G]):X;if(!T$($))return await W(Promise.resolve($));try{let X=0,Y=await W($.next());while(!Y.done)Z(Y.value,++X),Y=await W($.next());return Y.value}catch(X){throw $.return(void 0).catch(()=>{}),X}}function k$($,Z){if(typeof $==="string")return{progress:Z,message:$};if(typeof $==="object"&&$!==null){let{progress:z,total:G,message:W}=$;if(typeof z==="number"&&Number.isFinite(z))return{progress:z,...typeof G==="number"?{total:G}:{},...typeof W==="string"?{message:W}:{}};if(typeof W==="string")return{progress:Z,message:W}}return{progress:Z}}function Z9($,Z,z){let G=$.descriptor.name,W=`mcp_${crypto.randomUUID()}`,X=`mcp_call_${crypto.randomUUID()}`,Y=(Q)=>async()=>{throw new e(`"${G}" was called by a remote MCP client, which has no ${Q}.`,{retryable:!1})},K={put:Y("attachment store"),get:Y("attachment store"),read:Y("attachment store"),file:Y("attachment store")};return{context:Z,body:{},runId:W,toolCallId:X,signal:z,step:1,depth:0,resumed:!1,attachments:K,turn:Object.freeze({attachments:Object.freeze([])}),runAgent:Y("agent run to nest a sub-agent in"),generateImage:Y("attachment store to put an image in"),editImage:Y("attachment store to put an image in"),generate:(Q)=>N9({...Q,signal:Q.signal?AbortSignal.any([z,Q.signal]):z},{origin:{mcp:G,runId:W,toolCallId:X}}),jobs:J9({jobs:void 0,threadId:void 0,runId:W,toolCallId:X,toolName:G,depth:0,async:$.tool.async,attachments:null}),memory:F9()}}function W3($){if($.kind!=="tool"||$.meta.requiresApproval===!0)return;return $.tool.approvalPredicate}function z9($){return{context:$.context,body:$.body,runId:$.runId,threadId:$.threadId,toolCallId:$.toolCallId,step:$.step,depth:$.depth,signal:$.signal}}async function G9($,Z,z){try{return await $.tool.approvalPredicate(Z,z)!==!1}catch(G){return console.error(`[gemi/mcp] The requiresApproval of "${$.descriptor.name}" threw, so the call asks the user:`,G),!0}}function D3($){if($?.kind==="local"&&$.req)return;if($?.kind==="remote"&&$.req&&$.principal?.user&&typeof $.principal.id==="string"&&Array.isArray($.principal.scopes))return;throw Error('McpRegistry: a caller is { kind: "local", req } or { kind: "remote", req, principal }, with the principal a caller resolver verified.')}function W9($,Z){if(Z?.names&&!Z.names.includes($.name))return!1;if(Z?.tags&&!$.tags.some((z)=>Z.tags.includes(z)))return!1;return!0}var M$=10485760;function R$($){let Z=$?.maxBytes??M$;if(!Number.isSafeInteger(Z)||Z<=0)throw Error("McpRegistry: files.maxBytes must be a positive whole number of bytes.");let z=$?.fetchUrls===!0?{}:typeof $?.fetchUrls==="object"&&$.fetchUrls!==null?$.fetchUrls:null;return{maxBytes:Z,fetch:z}}function E$($){if($.length===0||$.length>255)return!1;for(let Z of $){let z=Z.codePointAt(0);if(z<32||z===127||Z==="/"||Z==="\\")return!1}return!0}var b$=/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}(\s*;.{0,200})?$/,y$=/^[A-Za-z0-9+/]*={0,2}$/;function f$($){return $.endsWith("==")?2:$.endsWith("=")?1:0}function X9($,Z,z){if(z!=="input"&&typeof z!=="function")throw Error(`${$}: ${Z} must be a function or "input".`)}function Y9($){return $ instanceof Object&&$.__internal_brand==="McpModelParam"}function w$($,Z,z){if(z.as!==void 0&&(typeof z.as!=="string"||z.as===""))throw Error(`${$}: ${Z}.as must be a non-empty string.`);let G=z.input;if(typeof G?.safeParse!=="function"||typeof G.toJSONSchema!=="function")throw Error(`${$}: ${Z}.input must be a schema, built with s.`);if(typeof z.bind!=="function")throw Error(`${$}: ${Z}.bind must be a function.`)}var v$=new Set(["readOnlyHint","destructiveHint","idempotentHint","openWorldHint"]);function g$($){switch($){case"GET":return{readOnlyHint:!0};case"DELETE":return{destructiveHint:!0};case"PUT":return{idempotentHint:!0};default:return{}}}function K9($,Z,z){if(z!==void 0&&(typeof z!=="object"||z===null))throw Error(`${$}: annotations must be an object of hints.`);let G=Z?g$(Z):{};for(let[W,X]of Object.entries(z??{})){if(!v$.has(W))throw Error(`${$}: "${W}" is not a tool annotation. Use readOnlyHint, destructiveHint, idempotentHint or openWorldHint.`);if(X===void 0)continue;if(typeof X!=="boolean")throw Error(`${$}: annotations.${W} must be true or false.`);G[W]=X}if(G.readOnlyHint===!0&&(G.destructiveHint||G.idempotentHint))throw Error(`${$} is read-only${z?.readOnlyHint?"":` (a ${Z})`} and also ${G.destructiveHint?"destructive":"idempotent"}, which only a tool that is not read-only can be. Set readOnlyHint: false if it changes something.`);return Object.freeze(G)}function Q9($,Z,z,G={}){let W=z.toJSONSchema(),X=Object.fromEntries(Object.entries(G).map(([Q,V])=>[Q,V.toJSONSchema()])),Y={...Z??{},type:"object",properties:{...Z?.properties??{},...W.properties,...X},required:[...Z?.required??[],...W.required??[],...Object.keys(G)],additionalProperties:!1},K=(Q)=>{if(typeof Q!=="object"||Q===null||Array.isArray(Q))return{ok:!1,errors:["expected an object"]};let V=[],H={};if($){let F=$.safeParse(Q);if(F.ok===!1)V.push(...F.errors);else H={...F.value}}let U=z.safeParse(Q);if(U.ok===!1)V.push(...U.errors);else H={...H,...U.value};for(let[F,O]of Object.entries(G)){let B=Q[F];if(B===void 0){V.push(`"${F}" is required`);continue}let I=O.safeParse(B);if(I.ok===!1)V.push(...I.errors.map((k)=>`${F}: ${k}`));else H[F]=I.value}return V.length>0?{ok:!1,errors:V}:{ok:!0,value:H}};return{toJSONSchema:()=>Y,safeParse:K,parse(Q){let V=K(Q);if(V.ok===!1)throw Error(V.errors.join(", "));return V.value}}}function h$($,Z){return Object.fromEntries(Object.entries($).filter(([z])=>!Z.includes(z)))}function m$($,Z,z){let G=Z.modifier==="*"||Z.modifier==="+"?z.split("/"):[z];if(G.some((W)=>W==="."||W===".."))throw new D(`"${z}" is not a valid value for "${Z.name}" of "${$}".`);return G.map(encodeURIComponent).join("/")}function O3($,Z,z){if(z===void 0||z===null)return;if(Array.isArray(z)){for(let G of z)O3($,Z,G);return}$.append(Z,typeof z==="object"?JSON.stringify(z):String(z))}function u$($){let Z=new URLSearchParams,z=(G,W)=>{if(W===void 0||W===null)return;if(Array.isArray(W)){for(let X of W)z(G,X);return}Z.append(G,typeof W==="object"?JSON.stringify(W):String(W))};for(let[G,W]of Object.entries($))z(G,W);return Z.toString()}async function V9($,Z){if(Z.status>=200&&Z.status<300){let G=Z.headers.get("Content-Type");if(G&&!S$.test(G)){let X=(await Z.arrayBuffer()).byteLength;return`"${$}" answered with ${X} bytes of ${G}, which is not shown.`}let W=await Z.text();if(W==="")return null;if(W.length>T)return`${W.slice(0,T)}\u2026 [cut at ${T} of ${W.length} characters]`;try{return JSON.parse(W)}catch{return W}}let z=await Z.text();if(Z.status>=400&&Z.status<500){let G=z.length>e3?`${z.slice(0,e3)}\u2026`:z;throw new D(`"${$}" was refused with ${Z.status}${G?`: ${G}`:"."}`,Z.status)}if(Z.status<500)console.error(`[gemi/mcp] "${$}" answered ${Z.status}${Z.headers.get("Location")?` to ${Z.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new D(`"${$}" failed on the server.`,Z.status)}import{createHash as d$,randomBytes as c$,timingSafeEqual as p$}from"crypto";function j3($){let Z=$.headers.get("authorization");if(!Z)return null;let z=/^Bearer[ ]+([\x21-\x7e]+)$/i.exec(Z.trim());return z?z[1]:null}class i${options;name="api-key";constructor($){this.options=$;if(typeof $?.prefix!=="string"||!/^[A-Za-z0-9_-]{3,32}$/.test($.prefix))throw Error('McpApiKeyResolver: prefix is required: 3 to 32 letters, digits, _ or -, like "myapp_mcp_".');if(typeof $.verify!=="function")throw Error("McpApiKeyResolver: verify must be a function that looks a key up.")}static generate($){return`${$}${c$(32).toString("base64url")}`}static hash($){return d$("sha256").update($).digest("hex")}static hashesMatch($,Z){let z=Buffer.from($),G=Buffer.from(Z);return z.length===G.length&&p$(z,G)}async resolve($){let Z=j3($);if(!Z?.startsWith(this.options.prefix))return null;let z=await this.options.verify(Z,$);if(!z)return{ok:!1,error:"invalid_token",description:"The API key is not valid."};if(!z.user||typeof z.id!=="string"||z.id===""||!Array.isArray(z.scopes))throw Error("McpApiKeyResolver: verify must answer { user, id, scopes } for a key it knows, or null.");return{ok:!0,principal:{user:z.user,via:this.name,id:z.id,scopes:[...z.scopes]}}}}import{createHash as R9,randomBytes as _9}from"crypto";async function x3($,Z){let z=l$();if(!z)return null;let G=await z.consume($,Z);return G.allowed?null:Math.max(1,Math.ceil(G.retryAfter/1000))}function l$(){try{let $=y();return $.bound(l)?$.make(l):null}catch{return null}}var A3=["2026-07-28"],Y3=["2025-11-25","2025-06-18","2025-03-26"],C3=[...A3,...Y3],S3="io.modelcontextprotocol/protocolVersion",q3="io.modelcontextprotocol/clientCapabilities";var T3="io.modelcontextprotocol/serverInfo",_={ParseError:-32700,InvalidRequest:-32600,MethodNotFound:-32601,InvalidParams:-32602,InternalError:-32603,HeaderMismatch:-32020,MissingRequiredClientCapability:-32021,UnsupportedProtocolVersion:-32022};class P extends Error{code;data;status;constructor($,Z,z,G=200){super(Z);this.code=$;this.data=z;this.status=G;this.name="JsonRpcError"}}function R($,Z){return{jsonrpc:"2.0",id:$,error:{code:Z.code,message:Z.message,...Z.data!==void 0?{data:Z.data}:{}}}}function P9($){if(Array.isArray($))throw new P(_.InvalidRequest,"Batched requests are not supported.",void 0,400);if(typeof $!=="object"||$===null||$.jsonrpc!=="2.0")throw new P(_.InvalidRequest,"Not a JSON-RPC 2.0 message.",void 0,400);let Z=$,z=Z.id,G=typeof z==="string"||typeof z==="number"&&Number.isFinite(z);if(typeof Z.method==="string"){if(Z.params!==void 0&&(typeof Z.params!=="object"||Z.params===null||Array.isArray(Z.params)))throw new P(_.InvalidRequest,"params must be an object.",void 0,400);if(z===void 0)return{kind:"notification",message:Z};if(!G)throw new P(_.InvalidRequest,"id must be a string or a number.",void 0,400);return{kind:"request",message:Z}}if(G&&(("result"in Z)||("error"in Z)))return{kind:"response",message:Z};throw new P(_.InvalidRequest,"Not a JSON-RPC request, notification or response.",void 0,400)}function k3($,Z){let z=$.outputSchema?.toJSONSchema(),G={...$.annotations,...$.title&&!Z?{title:$.title}:{}};return{name:$.name,...$.title?{title:$.title}:{},description:$.description,inputSchema:$.inputSchema.toJSONSchema(),...z&&(Z||z.type==="object")?{outputSchema:z}:{},...Object.keys(G).length>0?{annotations:G}:{}}}function S($){return typeof $==="object"&&$!==null&&!Array.isArray($)}function I9($){let Z=/^=\?base64\?([A-Za-z0-9+/]*={0,2})\?=$/.exec($);if(!Z)return $;try{let z=Buffer.from(Z[1],"base64");return new TextDecoder("utf-8",{fatal:!0}).decode(z)}catch{return null}}import{createHmac as n$,timingSafeEqual as t$}from"crypto";function M3($,Z,z){let G=Buffer.from(JSON.stringify(z)).toString("base64url");return`${Z}.${G}.${L9($,Z,G).toString("base64url")}`}function R3($,Z,z,G=Date.now()){if(typeof z!=="string"||z.length>8192)return null;let W=z.split(".");if(W.length!==3||W[0]!==Z)return null;let[,X,Y]=W,K=Buffer.from(Y,"base64url"),Q=L9($,Z,X);if(K.length!==Q.length||!t$(K,Q))return null;let V;try{V=JSON.parse(Buffer.from(X,"base64url").toString("utf8"))}catch{return null}if(typeof V!=="object"||V===null)return null;if(typeof V.exp!=="number"||V.exp<=G)return null;return V}function L9($,Z,z){return n$("sha256",K3($)).update(`${Z}.${z}`).digest()}var E3="gemi.mcp.request-state.v1",D9="gemi.mcp.session.v1",s$=86400000,O9=600000,j9="gemi_approval",b3=2000,o$={limit:600,window:60},a$={limit:30,window:60};class $3{registry;static token="router.mcp.http";endpoint;resolvers;context;allowedOrigins;rateLimit;maxBodyBytes;serverInfo;instructions;nonces;approvalTimeoutMs;feature;featureOn;pending=new Map;inflight=new Map;constructor($,Z,z={}){this.registry=$;if(typeof Z.url!=="string")throw Error(`route.mcp.remote.url is required: the endpoint's canonical URL, like "https://example.com/mcp".`);this.endpoint=$2(Z.url);let G=Z.resolvers??[];if(!Array.isArray(G)||G.length===0)throw Error("route.mcp.remote is enabled without a caller resolver. Configure route.mcp.remote.resolvers \u2014 an McpOAuthServer, an McpApiKeyResolver, or your own \u2014 or leave the endpoint off. Without one every request would be refused, or answered as nobody.");for(let W of G)if(typeof W?.resolve!=="function"||typeof W.name!=="string"||W.name==="")throw Error("route.mcp.remote.resolvers: each resolver needs a name and a resolve().");if(this.resolvers=G,K3(E3),this.context={resource:this.resource,registry:$},this.allowedOrigins=new Set((Z.allowedOrigins??[]).map((W)=>{if(new URL(W).origin!==W)throw Error(`route.mcp.remote.allowedOrigins: "${W}" is not an origin (scheme://host[:port]).`);return W})),this.allowedOrigins.add(this.endpoint.origin),this.rateLimit=Z.rateLimit===!1?!1:Z.rateLimit??o$,this.maxBodyBytes=Z.maxBodyBytes??Math.ceil($.maxFileBytes*1.4)+1048576,this.serverInfo={name:Z.server?.name??"gemi",version:Z.server?.version??"1.0.0"},this.instructions=Z.server?.instructions,this.nonces=Z.nonces??new k9,this.approvalTimeoutMs=Z.approvalTimeoutMs??O9,Z.feature!==void 0){if(typeof Z.feature!=="string"||Z.feature==="")throw Error("route.mcp.remote.feature must be a feature key from app/features.");this.feature=Z.feature}this.featureOn=z.featureOn??X3;for(let W of G)W.boot?.(this.context)}get resource(){return this.endpoint.href.replace(/\/$/,"")}async handle($){let Z=new URL($.url);if(Z.host!==this.endpoint.host)return null;if(this.feature!==void 0){if(!this.ownsPath(Z.pathname))return null;if(await this.featureOn(this.feature)!==!0)return null}for(let W of this.resolvers){let X=await W.handle?.($,this.context);if(X)return X}if(Z.pathname!==this.endpoint.pathname)return null;let z=$.headers.get("origin");if(z!==null&&!this.allowedOrigins.has(z))return f(R(null,new P(_.InvalidRequest,"Origin not allowed.")),403);let G=z?e$(z):{};if($.method==="OPTIONS")return new Response(null,{status:204,headers:{...G,"Access-Control-Allow-Methods":"POST","Access-Control-Allow-Headers":"Authorization, Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name, Last-Event-ID","Access-Control-Max-Age":"600"}});if($.method!=="POST")return x9(new Response(null,{status:405,headers:{Allow:"POST, OPTIONS"}}),G);return x9(await this.post($),G)}ownsPath($){let Z=this.endpoint.pathname.replace(/\/$/,"");return $===this.endpoint.pathname||$===Z||$.startsWith(`${Z}/`)||$.startsWith("/.well-known/")}async post($){let Z=null;for(let Y of this.resolvers){let K=await Y.resolve($,this.context);if(K===null)continue;if(K.ok===!1)return await this.failedAttempt($)??this.unauthorized(K.description);Z={...K.principal,via:Y.name};break}if(!Z)return(j3($)!==null?await this.failedAttempt($):null)??this.unauthorized();let z=await this.spend(Z);if(z)return z;let G=$.headers.get("content-type")??"";if(!/^application\/json\s*(;|$)/i.test(G))return new Response("Content-Type must be application/json.",{status:415});let W;try{W=JSON.parse(await r$($,this.maxBodyBytes))}catch(Y){if(Y instanceof V3)return f(R(null,new P(_.InvalidRequest,`The request is larger than ${this.maxBodyBytes} bytes.`)),413);return f(R(null,new P(_.ParseError,"Parse error.")),400)}let X=new m($,{},"api",this.endpoint.pathname);return await g.run(X,async()=>{try{return await this.dispatch($,X,Z,W)}catch(Y){if(Y instanceof P){let K=S(W)&&(typeof W.id==="string"||typeof W.id==="number")?W.id:null;return f(R(K,Y),Y.status)}return console.error("[gemi/mcp] The MCP endpoint failed:",Y),f(R(null,new P(_.InternalError,"Internal error.")),500)}},{user:Z.user})}async dispatch($,Z,z,G){let W=P9(G),X={kind:"remote",req:Z,principal:z};if(W.kind==="response"){let B=this.session($,z),I=this.pending.get(String(W.message.id));if(I&&I.session===B.id)this.pending.delete(String(W.message.id)),I.resolve(W.message);return new Response(null,{status:202})}let{message:Y}=W,K=Y.params??{},Q=S(K._meta)?K._meta:{};if(typeof Q[S3]==="string"){if(this.checkModernHeaders($,Y,Q[S3]),W.kind==="notification")return new Response(null,{status:202});return await this.respond(Y,async(B,I)=>this.modern({principal:z,caller:X,message:Y,params:K,era:{modern:!0},stream:B,signal:I}),C9(K),$.signal)}if(Y.method==="initialize"){if(W.kind!=="request")throw new P(_.InvalidRequest,"initialize is a request.",void 0,400);return this.initialize(W.message,K,z)}let V=this.session($,z),H=$.headers.get("mcp-protocol-version");if(H!==null&&H!==V.state.v)throw new P(_.InvalidRequest,`MCP-Protocol-Version ${H} is not the session's ${V.state.v}.`,void 0,400);if(W.kind==="notification"){if(Y.method==="notifications/cancelled"){let B=K.requestId;this.inflight.get(`${V.id}:${String(B)}`)?.abort()}return new Response(null,{status:202})}let U=`${V.id}:${String(Y.id)}`,F=new AbortController;this.inflight.set(U,F);let O=AbortSignal.any([F.signal,$.signal]);try{return await this.respond(Y,async(B,I)=>this.legacy({principal:z,caller:X,message:Y,params:K,era:{modern:!1,session:V.state,sessionId:V.id},stream:B,signal:I}),C9(K)||z2(this.registry,Y,V.state),O,()=>this.inflight.delete(U))}catch(B){throw this.inflight.delete(U),B}}async modern($){switch($.message.method){case"server/discover":return this.complete({supportedVersions:C3,capabilities:{tools:{}},...this.instructions?{instructions:this.instructions}:{},ttlMs:60000,cacheScope:"private"});case"tools/list":return this.complete({tools:(await this.registry.available($.caller)).map((Z)=>k3(Z,!0)),ttlMs:60000,cacheScope:"private"});case"tools/call":return await this.callTool($);default:throw new P(_.MethodNotFound,`Method not found: ${$.message.method}`,void 0,404)}}async legacy($){switch($.message.method){case"ping":return{};case"tools/list":return{tools:(await this.registry.available($.caller)).map((Z)=>k3(Z,!1))};case"tools/call":return await this.callTool($);default:throw new P(_.MethodNotFound,`Method not found: ${$.message.method}`)}}complete($){return{resultType:"complete",...$,_meta:{[T3]:this.serverInfo}}}initialize($,Z,z){let G=Z.protocolVersion,W=Y3.includes(G)?G:Y3[0],X=S(Z.capabilities)?Z.capabilities:{},Y={v:W,e:A9(X.elicitation),p:Q3(z),exp:Date.now()+s$},K=M3(D9,"gms1",Y);return f({jsonrpc:"2.0",id:$.id,result:{protocolVersion:W,capabilities:{tools:{listChanged:!1}},serverInfo:this.serverInfo,...this.instructions?{instructions:this.instructions}:{}}},200,{"Mcp-Session-Id":K})}session($,Z){let z=$.headers.get("mcp-session-id");if(!z)throw new P(_.InvalidRequest,"Missing Mcp-Session-Id. Send initialize first, or a request carrying io.modelcontextprotocol/protocolVersion in _meta.",void 0,400);let G=R3(D9,"gms1",z);if(!G||G.p!==Q3(Z))throw new P(_.InvalidRequest,"Session not found.",void 0,404);return{id:z,state:G}}checkModernHeaders($,Z,z){if(!A3.includes(z))throw new P(_.UnsupportedProtocolVersion,"Unsupported protocol version",{supported:C3,requested:z},400);let G=(Y)=>new P(_.HeaderMismatch,`Header mismatch: ${Y}`,void 0,400);if($.headers.get("mcp-protocol-version")!==z)throw G("MCP-Protocol-Version does not match the protocol version in _meta.");if($.headers.get("mcp-method")!==Z.method)throw G("Mcp-Method does not match the method.");let W=Z.params??{},X=Z.method==="tools/call"||Z.method==="prompts/get"?W.name:Z.method==="resources/read"?W.uri:void 0;if(X!==void 0){let Y=$.headers.get("mcp-name");if(Y===null||I9(Y)!==X)throw G("Mcp-Name does not match the request's name.")}}async callTool($){let{params:Z,caller:z,principal:G}=$,W=Z.name;if(typeof W!=="string")throw new P(_.InvalidParams,"tools/call needs a tool name.");let X=Z.arguments??{},Y=(await this.registry.available(z,{names:[W]}))[0];if(!Y)throw new P(_.InvalidParams,`Unknown tool: ${W}`);let K=!1;if(Y.requiresApproval){let F=Y.inputSchema.safeParse(X);if(F.ok===!1)return this.toolResult($,{content:[{type:"text",text:`Invalid arguments for "${W}": ${F.errors.join(", ")}`}],isError:!0});if($.era.modern&&S($.params.inputResponses)&&$.params.requestState!==void 0||await this.registry.approvalRequired(z,W,X,{signal:$.signal})){let B=await this.approval($,Y,X);if(B.kind==="answer")return B.result;K=!0}}let Q=G2($),V;try{V=await this.registry.execute(z,W,X,void 0,{approved:K,signal:$.signal,...Q?{progress:Q}:{}})}catch(F){if(F instanceof D)return this.toolResult($,{content:[{type:"text",text:F.message}],isError:!0});if(F instanceof v)throw new P(_.InvalidParams,F.reason==="unknown-tool"?`Unknown tool: ${W}`:F.message);return console.error(`[gemi/mcp] "${W}" failed for ${G.via}:${G.id}:`,F),this.toolResult($,{content:[{type:"text",text:`"${W}" failed on the server.`}],isError:!0})}let H=typeof V==="string"?V:JSON.stringify(V??null),U=Y.outputSchema&&($.era.modern||S(V))?{structuredContent:V}:{};return this.toolResult($,{content:[{type:"text",text:H}],...U,isError:!1})}toolResult($,Z){return $.era.modern?this.complete(Z):Z}async approval($,Z,z){let G=W2(Z,z),W=(V)=>({kind:"answer",result:this.toolResult($,{content:[{type:"text",text:V}],isError:!0})});if($.era.modern){let V=S($.params._meta)?$.params._meta:{},H=S(V[q3])?V[q3]:{},U=S($.params.inputResponses)?$.params.inputResponses:null,F=$.params.requestState,O=Z2(z);if(U&&F!==void 0){let I=R3(E3,"grs1",F);if(!I||I.p!==Q3($.principal)||I.t!==Z.name||I.a!==O)throw new P(_.InvalidParams,"requestState is not valid for this call.");if(!await this.spendNonce(I.n,I.exp))throw new P(_.InvalidParams,"requestState has already been used.");return S9(U[j9])?{kind:"approved"}:W(`The user did not approve "${Z.name}", and it did not run.`)}if(!A9(H.elicitation))throw new P(_.MissingRequiredClientCapability,`"${Z.name}" needs the user's approval, and this client cannot ask for it (elicitation).`,{requiredCapabilities:{elicitation:{form:{}}}},400);let B={p:Q3($.principal),t:Z.name,a:O,n:_9(16).toString("base64url"),exp:Date.now()+O9};return{kind:"answer",result:{resultType:"input_required",inputRequests:{[j9]:{method:"elicitation/create",params:{mode:"form",...G}}},requestState:M3(E3,"grs1",B),_meta:{[T3]:this.serverInfo}}}}let X=$.era;if(!X.session.e||!$.stream)return W(`"${Z.name}" needs the user's approval, and this client cannot ask for it (elicitation). It did not run.`);let Y=`gemi-approval-${_9(12).toString("base64url")}`,K=X.sessionId,Q=await new Promise((V)=>{let H=setTimeout(()=>{this.pending.delete(Y),V(null)},this.approvalTimeoutMs),U=(F)=>{clearTimeout(H),V(F)};this.pending.set(Y,{session:K,resolve:U}),$.signal.addEventListener("abort",()=>{this.pending.delete(Y),U(null)}),$.stream.send({jsonrpc:"2.0",id:Y,method:"elicitation/create",params:X.session.v==="2025-11-25"?{mode:"form",...G}:G})});return Q&&!Q.error&&S9(Q.result)?{kind:"approved"}:W(`The user did not approve "${Z.name}", and it did not run.`)}async spendNonce($,Z){try{return await this.nonces.consume($,Z)}catch(z){return console.error("[gemi/mcp] Spending an approval failed:",z),!1}}async respond($,Z,z,G,W){if(!z)try{let Q=await Z(null,G);return f({jsonrpc:"2.0",id:$.id,result:Q},200)}finally{W?.()}let X=new AbortController,Y=AbortSignal.any([X.signal,G]),K=new E9(()=>X.abort());return(async()=>{try{let Q=await Z(K,Y);K.send({jsonrpc:"2.0",id:$.id,result:Q})}catch(Q){if(Q instanceof P)K.send(R($.id,Q));else console.error("[gemi/mcp] The MCP endpoint failed:",Q),K.send(R($.id,new P(_.InternalError,"Internal error.")))}finally{W?.(),K.close()}})(),new Response(K.readable,{status:200,headers:{"Content-Type":"text/event-stream","Cache-Control":"no-store","X-Accel-Buffering":"no"}})}unauthorized($){let Z=this.resolvers.map((G)=>G.challenge?.(this.context)).find(Boolean)??'Bearer realm="mcp"',z=$?`${Z}, error="invalid_token", error_description="${$.replace(/["\\]/g,"")}"`:Z;return f(R(null,new P(_.InvalidRequest,"Unauthorized.")),401,{"WWW-Authenticate":z})}async failedAttempt($){let Z=T9(new m($,{},"api",this.endpoint.pathname)),z=await x3(`mcp-auth-failed:${Z}`,a$);return z===null?null:q9(z)}async spend($){if(!this.rateLimit)return null;let Z=await x3(`mcp-endpoint:${$.via}:${$.id}`,this.rateLimit);return Z===null?null:q9(Z)}}class E9{readable;controller;closed=!1;encoder=new TextEncoder;constructor($){this.readable=new ReadableStream({start:(Z)=>{this.controller=Z},cancel:()=>{this.closed=!0,$()}})}send($){if(this.closed)return;this.controller.enqueue(this.encoder.encode(`event: message
|
|
3
3
|
data: ${JSON.stringify($)}
|
|
4
4
|
|
|
5
5
|
`))}close(){if(this.closed)return;this.closed=!0,this.controller.close()}}class V3 extends Error{}async function r$($,Z){let z=Number($.headers.get("content-length"));if(Number.isFinite(z)&&z>Z)throw new V3;if(!$.body)return"";let G=$.body.getReader(),W=[],X=0;for(;;){let{done:Y,value:K}=await G.read();if(Y)break;if(X+=K.byteLength,X>Z)throw await G.cancel(),new V3;W.push(K)}return Buffer.concat(W).toString("utf8")}function f($,Z,z={}){return new Response(JSON.stringify($),{status:Z,headers:{"Content-Type":"application/json","Cache-Control":"no-store",...z}})}function x9($,Z){for(let[z,G]of Object.entries(Z))$.headers.set(z,G);return $}function e$($){return{"Access-Control-Allow-Origin":$,"Access-Control-Expose-Headers":"Mcp-Session-Id, WWW-Authenticate",Vary:"Origin"}}function $2($){let Z;try{Z=new URL($)}catch{throw Error(`route.mcp.remote.url: "${$}" is not a URL.`)}let z=Z.hostname==="localhost"||Z.hostname==="127.0.0.1"||Z.hostname==="[::1]";if(Z.protocol!=="https:"&&!(Z.protocol==="http:"&&z))throw Error("route.mcp.remote.url must be https (http only on localhost): tokens travel to it.");if(Z.search||Z.hash||Z.username||Z.password)throw Error("route.mcp.remote.url must have no query, fragment or credentials.");if(Z.pathname==="/"||Z.pathname.endsWith("/"))throw Error('route.mcp.remote.url needs a path of its own, like "/mcp", without a trailing slash.');return Z}function Q3($){return R9("sha256").update(`${$.via}\x00${$.id}\x00${$.user?.id}`).digest("base64url").slice(0,32)}function Z2($){return R9("sha256").update(M9($??{})).digest("base64url")}function A9($){if(!S($))return!1;if(Object.keys($).length===0)return!0;return"form"in $}function C9($){let Z=S($._meta)?$._meta:{};return typeof Z.progressToken==="string"||typeof Z.progressToken==="number"}function z2($,Z,z){if(Z.method!=="tools/call"||!z.e)return!1;let G=Z.params?.name;return typeof G==="string"&&$.descriptors({names:[G]})[0]?.requiresApproval===!0}function G2($){let z=(S($.params._meta)?$.params._meta:{}).progressToken;if(!$.stream||typeof z!=="string"&&typeof z!=="number")return;let G=-1/0;return(W)=>{if(typeof W?.progress!=="number"||!Number.isFinite(W.progress)||W.progress<=G)return;G=W.progress,$.stream.send({jsonrpc:"2.0",method:"notifications/progress",params:{progressToken:z,progress:W.progress,...typeof W.total==="number"?{total:W.total}:{},...typeof W.message==="string"?{message:W.message.slice(0,1000)}:{}}})}}function W2($,Z){let z=JSON.stringify(Z??{},null,1),G=z.length>b3?`${z.slice(0,b3)}\u2026 (${z.length-b3} more characters not shown)`:z;return{message:`Allow "${$.title??$.name}" to run? ${$.description}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{R as S,T as h,U as u}from"./chunk-
|
|
2
|
+
import{R as S,T as h,U as u}from"./chunk-b97tpvet.js";import{ma as q,na as w}from"./chunk-eydahc2f.js";import"./chunk-a22h5cby.js";import"./chunk-epvpmakp.js";import"./chunk-r3v2cnrj.js";import"./chunk-vp4x2c0k.js";import"./chunk-kk18xqgc.js";import"./chunk-3v4esxbd.js";import{Gi as N}from"./chunk-v2cv3wcw.js";import{Yk as y}from"./chunk-apfk702c.js";import{ll as C}from"./chunk-5aj27bgc.js";import"./chunk-tbhtx2t0.js";import{join as $J}from"path";import{createServer as zJ}from"vite";import{join as l}from"path";import{IncomingMessage as d,ServerResponse as p}from"http";import{Socket as i}from"net";function c(J){return J.replace(/\x1b\[[0-9;]*m/g,"").replace(/\[[0-9;]*m/g,"").replace(/\n\s*\n/g,`
|
|
3
3
|
`).split(`
|
|
4
4
|
`).map((W)=>W.trim()).filter((W)=>W.length>0).join(`
|
|
5
5
|
`).trim()}function _(J){return`
|