gemi 0.113.0 → 0.114.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/ai/index.js +32 -32
- package/dist/ai/index.js.map +3 -5
- package/dist/app/App.d.ts.map +1 -1
- package/dist/app/index.js +1 -1
- package/dist/bin/gemi.js +8 -1
- package/dist/bin/gemi.js.map +3 -3
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/chunk-0n9vs1ad.js +14 -0
- package/dist/{chunk-cqnfp0n9.js.map → chunk-0n9vs1ad.js.map} +4 -4
- package/dist/{chunk-68s1f5d5.js → chunk-1f9sqht5.js} +2 -2
- package/dist/{chunk-68s1f5d5.js.map → chunk-1f9sqht5.js.map} +1 -1
- package/dist/{chunk-qt8nvpqp.js → chunk-1p0dazvk.js} +2 -2
- package/dist/{chunk-qt8nvpqp.js.map → chunk-1p0dazvk.js.map} +1 -1
- package/dist/{chunk-3f1kq32g.js → chunk-2rphhsr7.js} +2 -2
- package/dist/{chunk-3f1kq32g.js.map → chunk-2rphhsr7.js.map} +1 -1
- package/dist/{chunk-qx87cdfg.js → chunk-2z1981d0.js} +2 -2
- package/dist/{chunk-qx87cdfg.js.map → chunk-2z1981d0.js.map} +1 -1
- package/dist/{chunk-zng3qexn.js → chunk-329ggbcw.js} +2 -2
- package/dist/{chunk-zng3qexn.js.map → chunk-329ggbcw.js.map} +1 -1
- package/dist/{chunk-v4gwmhtf.js → chunk-37qk5dh8.js} +2 -2
- package/dist/{chunk-v4gwmhtf.js.map → chunk-37qk5dh8.js.map} +1 -1
- package/dist/{chunk-7jnpnjth.js → chunk-3ma9m432.js} +2 -2
- package/dist/{chunk-7jnpnjth.js.map → chunk-3ma9m432.js.map} +1 -1
- package/dist/{chunk-fnkjmq94.js → chunk-4szjp84e.js} +2 -2
- package/dist/{chunk-fnkjmq94.js.map → chunk-4szjp84e.js.map} +1 -1
- package/dist/{chunk-fr0arqdj.js → chunk-56fgk5rq.js} +2 -2
- package/dist/{chunk-fr0arqdj.js.map → chunk-56fgk5rq.js.map} +1 -1
- package/dist/{chunk-y6v0x869.js → chunk-583vxa2v.js} +2 -2
- package/dist/{chunk-y6v0x869.js.map → chunk-583vxa2v.js.map} +1 -1
- package/dist/{chunk-vqa3agzw.js → chunk-5j76r5q8.js} +3 -3
- package/dist/{chunk-vqa3agzw.js.map → chunk-5j76r5q8.js.map} +1 -1
- package/dist/chunk-60nyk1w8.js +10 -0
- package/dist/chunk-60nyk1w8.js.map +38 -0
- package/dist/{chunk-vwprq300.js → chunk-6h9j3f1x.js} +2 -2
- package/dist/{chunk-vwprq300.js.map → chunk-6h9j3f1x.js.map} +1 -1
- package/dist/{chunk-r7b12dmm.js → chunk-75wk4jxd.js} +2 -2
- package/dist/{chunk-r7b12dmm.js.map → chunk-75wk4jxd.js.map} +1 -1
- package/dist/{chunk-de03dewf.js → chunk-7fwp7vwh.js} +2 -2
- package/dist/{chunk-de03dewf.js.map → chunk-7fwp7vwh.js.map} +1 -1
- package/dist/{chunk-9vsj0ksk.js → chunk-7rmjr8p7.js} +3 -3
- package/dist/{chunk-9vsj0ksk.js.map → chunk-7rmjr8p7.js.map} +1 -1
- package/dist/{chunk-atmzm0z3.js → chunk-7v8sgqen.js} +2 -2
- package/dist/{chunk-atmzm0z3.js.map → chunk-7v8sgqen.js.map} +1 -1
- package/dist/{chunk-4t1pbt9p.js → chunk-8c2fs0n0.js} +3 -3
- package/dist/{chunk-4t1pbt9p.js.map → chunk-8c2fs0n0.js.map} +1 -1
- package/dist/{chunk-vzf5pbx3.js → chunk-8h7dh2h2.js} +2 -2
- package/dist/{chunk-vzf5pbx3.js.map → chunk-8h7dh2h2.js.map} +1 -1
- package/dist/{chunk-c7mp2kyp.js → chunk-8k8534kg.js} +2 -2
- package/dist/{chunk-c7mp2kyp.js.map → chunk-8k8534kg.js.map} +1 -1
- package/dist/{chunk-g8kx549d.js → chunk-8rt4wx8z.js} +3 -3
- package/dist/{chunk-g8kx549d.js.map → chunk-8rt4wx8z.js.map} +1 -1
- package/dist/{chunk-bq5nqqeq.js → chunk-a5sbgffr.js} +1 -1
- package/dist/{chunk-szr1k584.js → chunk-ap5gek56.js} +2 -2
- package/dist/{chunk-szr1k584.js.map → chunk-ap5gek56.js.map} +1 -1
- package/dist/{chunk-4tp2cr02.js → chunk-awp3n119.js} +2 -2
- package/dist/{chunk-4tp2cr02.js.map → chunk-awp3n119.js.map} +1 -1
- package/dist/{chunk-5q4a7yqf.js → chunk-cna105t6.js} +2 -2
- package/dist/{chunk-5q4a7yqf.js.map → chunk-cna105t6.js.map} +1 -1
- package/dist/{chunk-rg4hhxj3.js → chunk-d4m4amy4.js} +2 -2
- package/dist/{chunk-rg4hhxj3.js.map → chunk-d4m4amy4.js.map} +1 -1
- package/dist/{chunk-8mbyyh8j.js → chunk-e6fp6dxx.js} +2 -2
- package/dist/{chunk-8mbyyh8j.js.map → chunk-e6fp6dxx.js.map} +1 -1
- package/dist/{chunk-1se4t9rg.js → chunk-e7axa0d5.js} +3 -3
- package/dist/{chunk-1se4t9rg.js.map → chunk-e7axa0d5.js.map} +1 -1
- package/dist/{chunk-9tpx9ag3.js → chunk-erxv1jz7.js} +2 -2
- package/dist/{chunk-9tpx9ag3.js.map → chunk-erxv1jz7.js.map} +1 -1
- package/dist/chunk-fakvf9wb.js +5 -0
- package/dist/{chunk-fsfbyg5e.js.map → chunk-fakvf9wb.js.map} +1 -1
- package/dist/{chunk-nysqgzp1.js → chunk-fv9mr1t4.js} +2 -2
- package/dist/{chunk-nysqgzp1.js.map → chunk-fv9mr1t4.js.map} +1 -1
- package/dist/{chunk-p3zmc8k8.js → chunk-gdmsn3f5.js} +2 -2
- package/dist/{chunk-p3zmc8k8.js.map → chunk-gdmsn3f5.js.map} +1 -1
- package/dist/{chunk-y6frz1vm.js → chunk-gv91vjeb.js} +1 -1
- package/dist/{chunk-5x0aacrn.js → chunk-h2r2rdhj.js} +2 -2
- package/dist/{chunk-5x0aacrn.js.map → chunk-h2r2rdhj.js.map} +1 -1
- package/dist/{chunk-1p85fdrk.js → chunk-h4pazcgg.js} +2 -2
- package/dist/{chunk-1p85fdrk.js.map → chunk-h4pazcgg.js.map} +1 -1
- package/dist/{chunk-mk0rtgd1.js → chunk-hrvzchxd.js} +2 -2
- package/dist/{chunk-mk0rtgd1.js.map → chunk-hrvzchxd.js.map} +1 -1
- package/dist/{chunk-w45agtch.js → chunk-hytt9qyj.js} +2 -2
- package/dist/{chunk-w45agtch.js.map → chunk-hytt9qyj.js.map} +1 -1
- package/dist/{chunk-5nqkwa2w.js → chunk-hz6yzzbz.js} +1 -1
- package/dist/{chunk-z5hrymvn.js → chunk-j3nr4cg0.js} +3 -3
- package/dist/{chunk-z5hrymvn.js.map → chunk-j3nr4cg0.js.map} +1 -1
- package/dist/{chunk-dbwhgyg3.js → chunk-jk97wv0q.js} +2 -2
- package/dist/{chunk-dbwhgyg3.js.map → chunk-jk97wv0q.js.map} +1 -1
- package/dist/{chunk-x5sert1k.js → chunk-jv4g8grc.js} +2 -2
- package/dist/{chunk-x5sert1k.js.map → chunk-jv4g8grc.js.map} +1 -1
- package/dist/{chunk-yyzexrnb.js → chunk-jyrsd1dn.js} +2 -2
- package/dist/{chunk-yyzexrnb.js.map → chunk-jyrsd1dn.js.map} +1 -1
- package/dist/{chunk-y596ghpz.js → chunk-jzp0fs6h.js} +2 -2
- package/dist/{chunk-y596ghpz.js.map → chunk-jzp0fs6h.js.map} +1 -1
- package/dist/{chunk-kz4yp5n7.js → chunk-kcx468qv.js} +2 -2
- package/dist/{chunk-kz4yp5n7.js.map → chunk-kcx468qv.js.map} +1 -1
- package/dist/{chunk-zyer1g7k.js → chunk-kg1nbxvm.js} +4 -4
- package/dist/{chunk-zyer1g7k.js.map → chunk-kg1nbxvm.js.map} +1 -1
- package/dist/{chunk-qpttgn4k.js → chunk-kqb4s1r0.js} +3 -3
- package/dist/{chunk-qpttgn4k.js.map → chunk-kqb4s1r0.js.map} +1 -1
- package/dist/{chunk-rh2fad7n.js → chunk-m17ktssc.js} +1 -1
- package/dist/{chunk-k2fh4r0p.js → chunk-m2yfvwrn.js} +2 -2
- package/dist/{chunk-k2fh4r0p.js.map → chunk-m2yfvwrn.js.map} +1 -1
- package/dist/{chunk-wr3e4sez.js → chunk-mqcy5vja.js} +2 -2
- package/dist/{chunk-wr3e4sez.js.map → chunk-mqcy5vja.js.map} +1 -1
- package/dist/{chunk-f1y8eggf.js → chunk-n672d4g8.js} +2 -2
- package/dist/{chunk-f1y8eggf.js.map → chunk-n672d4g8.js.map} +1 -1
- package/dist/{chunk-xqwdn9kb.js → chunk-nek1gzz2.js} +3 -3
- package/dist/{chunk-xqwdn9kb.js.map → chunk-nek1gzz2.js.map} +1 -1
- package/dist/{chunk-01qd6xx3.js → chunk-pmywkp1w.js} +3 -3
- package/dist/{chunk-01qd6xx3.js.map → chunk-pmywkp1w.js.map} +1 -1
- package/dist/chunk-q95k1ymq.js +4 -0
- package/dist/{chunk-kp7k4s5c.js.map → chunk-q95k1ymq.js.map} +3 -3
- package/dist/{chunk-vdwagp7y.js → chunk-r4namj39.js} +2 -2
- package/dist/{chunk-vdwagp7y.js.map → chunk-r4namj39.js.map} +1 -1
- package/dist/{chunk-571rvxe6.js → chunk-rrqr1dwq.js} +2 -2
- package/dist/{chunk-571rvxe6.js.map → chunk-rrqr1dwq.js.map} +1 -1
- package/dist/{chunk-sr18y2ck.js → chunk-tp3mzj4v.js} +2 -2
- package/dist/{chunk-sr18y2ck.js.map → chunk-tp3mzj4v.js.map} +1 -1
- package/dist/{chunk-6wymbbes.js → chunk-ty4aj41k.js} +3 -3
- package/dist/{chunk-6wymbbes.js.map → chunk-ty4aj41k.js.map} +1 -1
- package/dist/chunk-v9na3dny.js +46 -0
- package/dist/{chunk-6g20zec2.js.map → chunk-v9na3dny.js.map} +6 -6
- package/dist/{chunk-d04r0hdf.js → chunk-w2fpr99k.js} +2 -2
- package/dist/{chunk-d04r0hdf.js.map → chunk-w2fpr99k.js.map} +1 -1
- package/dist/{chunk-vc9phr57.js → chunk-wwmdqhzy.js} +2 -2
- package/dist/{chunk-vc9phr57.js.map → chunk-wwmdqhzy.js.map} +1 -1
- package/dist/chunk-wysm6qte.js +5 -0
- package/dist/chunk-wysm6qte.js.map +12 -0
- package/dist/{chunk-mepaedy5.js → chunk-x157wrgy.js} +2 -2
- package/dist/{chunk-mepaedy5.js.map → chunk-x157wrgy.js.map} +1 -1
- package/dist/{chunk-nksn6xcd.js → chunk-xrh6k13s.js} +2 -2
- package/dist/{chunk-nksn6xcd.js.map → chunk-xrh6k13s.js.map} +1 -1
- package/dist/{chunk-g8sc6scv.js → chunk-y0wx6w80.js} +2 -2
- package/dist/{chunk-g8sc6scv.js.map → chunk-y0wx6w80.js.map} +1 -1
- package/dist/{chunk-kexsfyjj.js → chunk-yp5979we.js} +2 -2
- package/dist/{chunk-kexsfyjj.js.map → chunk-yp5979we.js.map} +1 -1
- package/dist/{chunk-essqp1ek.js → chunk-z32xfe8c.js} +2 -2
- package/dist/{chunk-essqp1ek.js.map → chunk-z32xfe8c.js.map} +1 -1
- package/dist/{chunk-x5yq097b.js → chunk-zgn0p8qr.js} +3 -3
- package/dist/{chunk-x5yq097b.js.map → chunk-zgn0p8qr.js.map} +1 -1
- package/dist/{chunk-agw3anaf.js → chunk-zns7xcz9.js} +1 -1
- package/dist/config/index.js +1 -1
- package/dist/console/run.js +2 -2
- package/dist/console/run.js.map +1 -1
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/http/HttpRequest.d.ts +31 -0
- package/dist/http/HttpRequest.d.ts.map +1 -1
- package/dist/http/McpRouter.d.ts +54 -1
- package/dist/http/McpRouter.d.ts.map +1 -1
- package/dist/http/RateLimitMiddleware.d.ts +4 -1
- package/dist/http/RateLimitMiddleware.d.ts.map +1 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/http/modelOriginated.d.ts +36 -2
- package/dist/http/modelOriginated.d.ts.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +2 -2
- package/dist/i18n/dictionaryRuntime.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +1 -1
- package/dist/kernel/Kernel.d.ts +3 -0
- package/dist/kernel/Kernel.d.ts.map +1 -1
- package/dist/kernel/index.js +3 -3
- package/dist/kernel/index.js.map +3 -3
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/services/index.d.ts +6 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +44 -9
- package/dist/services/index.js.map +6 -3
- package/dist/services/mcp/McpRegistry.d.ts +190 -29
- package/dist/services/mcp/McpRegistry.d.ts.map +1 -1
- package/dist/services/mcp/http/McpHttpServer.d.ts +153 -0
- package/dist/services/mcp/http/McpHttpServer.d.ts.map +1 -0
- package/dist/services/mcp/http/callers.d.ts +131 -0
- package/dist/services/mcp/http/callers.d.ts.map +1 -0
- package/dist/services/mcp/http/limits.d.ts +11 -0
- package/dist/services/mcp/http/limits.d.ts.map +1 -0
- package/dist/services/mcp/http/oauth/McpOAuthServer.d.ts +150 -0
- package/dist/services/mcp/http/oauth/McpOAuthServer.d.ts.map +1 -0
- package/dist/services/mcp/http/oauth/consentPage.d.ts +14 -0
- package/dist/services/mcp/http/oauth/consentPage.d.ts.map +1 -0
- package/dist/services/mcp/http/oauth/store.d.ts +98 -0
- package/dist/services/mcp/http/oauth/store.d.ts.map +1 -0
- package/dist/services/mcp/http/protocol.d.ts +95 -0
- package/dist/services/mcp/http/protocol.d.ts.map +1 -0
- package/dist/services/mcp/http/signedState.d.ts +25 -0
- package/dist/services/mcp/http/signedState.d.ts.map +1 -0
- package/dist/services/queue/work.js +2 -2
- package/dist/services/queue/work.js.map +1 -1
- package/dist/services/router/ApiRouteDispatcher.d.ts +29 -0
- package/dist/services/router/ApiRouteDispatcher.d.ts.map +1 -1
- package/dist/services/router/RouteServiceProvider.d.ts +6 -0
- package/dist/services/router/RouteServiceProvider.d.ts.map +1 -1
- package/dist/services/router/config.d.ts +16 -0
- package/dist/services/router/config.d.ts.map +1 -1
- package/dist/stats/route-table.js +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +1 -1
- package/dist/vitest/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3z9vhjd7.js +0 -5
- package/dist/chunk-3z9vhjd7.js.map +0 -10
- package/dist/chunk-5zzhypmt.js +0 -5
- package/dist/chunk-5zzhypmt.js.map +0 -33
- package/dist/chunk-6g20zec2.js +0 -46
- package/dist/chunk-cqnfp0n9.js +0 -14
- package/dist/chunk-fsfbyg5e.js +0 -5
- package/dist/chunk-kp7k4s5c.js +0 -4
- /package/dist/{chunk-bq5nqqeq.js.map → chunk-a5sbgffr.js.map} +0 -0
- /package/dist/{chunk-y6frz1vm.js.map → chunk-gv91vjeb.js.map} +0 -0
- /package/dist/{chunk-5nqkwa2w.js.map → chunk-hz6yzzbz.js.map} +0 -0
- /package/dist/{chunk-rh2fad7n.js.map → chunk-m17ktssc.js.map} +0 -0
- /package/dist/{chunk-agw3anaf.js.map → chunk-zns7xcz9.js.map} +0 -0
package/package.json
CHANGED
package/dist/chunk-3z9vhjd7.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
class x extends Error{retryable;constructor(b,j={}){super(b,j.cause!==void 0?{cause:j.cause}:void 0);this.name="ToolError",this.retryable=j.retryable??!0}}var D={provider_error:"The model provider returned an error.",rate_limited:"The model provider is rate limiting requests. Try again shortly.",context_length_exceeded:"The conversation is too long for the model.",content_filtered:"The model provider's content filter blocked this request.",invalid_tool_input:"The model provider rejected the request.",tool_error:"A tool failed.",unknown:"The run failed unexpectedly."};function v(b,j){if(j.source==="tool"){if(j.cause instanceof x)return b;return{...b,message:`The tool "${j.toolName}" failed with an unexpected error.`}}let q=D[b.code];return q===void 0?b:{...b,message:q}}function H(b,j,q,z){let k;try{k=(b??v)(j,q)}catch(B){z(B),k=v(j,q)}return{code:k.code,message:k.message,...k.toolCallId!==void 0?{toolCallId:k.toolCallId}:{},retryable:k.retryable}}var y=new WeakMap;function J(b,j){if(b!==j)y.set(b,j)}function K(b){return y.get(b)??b}
|
|
3
|
-
export{x as Kb,v as Lb,H as Mb,J as Nb,K as Ob};
|
|
4
|
-
|
|
5
|
-
//# debugId=50CFBD8E3EF3379764756E2164756E21
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../ai/redact.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import type { AgentError, AgentErrorCode } from \"./types\";\n\n/**\n * A tool failure whose message is meant to be read: by the model, which can\n * act on it, and by the client, which shows it.\n *\n * Any other exception a tool throws reaches the model and the client as a\n * generic sentence (see `redactError`), because its message is whatever the\n * code underneath happened to say: a database driver's connection string, an\n * internal hostname, a third party's error body. The full error is logged.\n * Throw this instead when the message is written for the person:\n *\n * ```ts\n * if (!order) throw new ToolError(`There is no order ${input.orderId}.`);\n * ```\n */\nexport class ToolError extends Error {\n /** Whether the model may try the call again. Default `true`. */\n readonly retryable: boolean;\n\n constructor(message: string, options: { retryable?: boolean; cause?: unknown } = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"ToolError\";\n this.retryable = options.retryable ?? true;\n }\n}\n\n/**\n * Where an error being redacted came from, with the unredacted detail.\n *\n * - `run`: the run's own failure, on its `error` frame. `failure` is what\n * `result().error` returns: the provider's own message, its HTTP status and\n * request id.\n * - `tool`: a tool threw. `cause` is what it threw; the result the model and\n * the client read is the redacted one.\n */\nexport type ErrorRedactionInfo =\n | {\n source: \"run\";\n failure: AgentError & { status?: number; requestId?: string };\n }\n | {\n source: \"tool\";\n toolName: string;\n cause: unknown;\n };\n\n/**\n * Decides what a client (and, for a tool, the model) is told about a failure.\n * Return the error to send. Full detail is never lost: it stays on\n * `result().error`, in the log and in the controller's `onError`.\n */\nexport type ErrorRedactor = (error: AgentError, info: ErrorRedactionInfo) => AgentError;\n\n/**\n * The run failures whose message comes from outside gemi: a provider's error\n * body or an exception's text. Those can carry resource and deployment names,\n * internal URLs, request ids and, in a provider's validation message, pieces of\n * the request itself. The rest (`timeout`, `invalid_output`, `aborted`, ...)\n * are sentences gemi wrote and are kept.\n */\nconst GENERIC_RUN_MESSAGES: Partial<Record<AgentErrorCode, string>> = {\n provider_error: \"The model provider returned an error.\",\n rate_limited: \"The model provider is rate limiting requests. Try again shortly.\",\n context_length_exceeded: \"The conversation is too long for the model.\",\n content_filtered: \"The model provider's content filter blocked this request.\",\n invalid_tool_input: \"The model provider rejected the request.\",\n tool_error: \"A tool failed.\",\n unknown: \"The run failed unexpectedly.\",\n};\n\n/**\n * The default redaction, and what an override of `AgentController.redactError`\n * falls back to.\n *\n * Keeps `code`, `retryable` and `toolCallId`, which are what a client branches\n * on, and replaces a message that did not come from gemi with a fixed sentence\n * for its code. A tool's exception keeps its message only when it is a\n * `ToolError`.\n */\nexport function redactError(error: AgentError, info: ErrorRedactionInfo): AgentError {\n if (info.source === \"tool\") {\n if (info.cause instanceof ToolError) return error;\n return {\n ...error,\n message: `The tool \"${info.toolName}\" failed with an unexpected error.`,\n };\n }\n const generic = GENERIC_RUN_MESSAGES[error.code];\n return generic === undefined ? error : { ...error, message: generic };\n}\n\n/**\n * Runs an app's redactor and keeps only the `AgentError` fields of what it\n * returns, so a redactor that spreads the failure (`{ ...info.failure }`)\n * cannot put its status or request id on the wire by accident. A redactor\n * that throws falls back to the default rather than to the unredacted error.\n */\nexport function applyRedaction(\n redactor: ErrorRedactor | undefined,\n error: AgentError,\n info: ErrorRedactionInfo,\n onFailure: (err: unknown) => void,\n): AgentError {\n let out: AgentError;\n try {\n out = (redactor ?? redactError)(error, info);\n } catch (err) {\n onFailure(err);\n out = redactError(error, info);\n }\n return {\n code: out.code,\n message: out.message,\n ...(out.toolCallId !== undefined ? { toolCallId: out.toolCallId } : {}),\n retryable: out.retryable,\n };\n}\n\n/**\n * The unredacted error behind a redacted one, for the server-side hooks.\n *\n * Keyed by the object put on the frame, which is the object the controller's\n * event hooks are handed in the same process; it never crosses a wire.\n */\nconst unredacted = new WeakMap<AgentError, AgentError>();\n\nexport function rememberUnredacted(redacted: AgentError, original: AgentError): void {\n if (redacted !== original) unredacted.set(redacted, original);\n}\n\n/** The original of `error` when it was redacted in this process, else `error`. */\nexport function unredactedError(error: AgentError): AgentError {\n return unredacted.get(error) ?? error;\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": ";AAgBO,MAAM,UAAkB,KAAM,CAE1B,UAET,WAAW,CAAC,EAAiB,EAAoD,CAAC,EAAG,CACnF,MAAM,EAAS,EAAQ,QAAU,OAAY,CAAE,MAAO,EAAQ,KAAM,EAAI,MAAS,EACjF,KAAK,KAAO,YACZ,KAAK,UAAY,EAAQ,WAAa,GAE1C,CAoCA,IAAM,EAAgE,CACpE,eAAgB,wCAChB,aAAc,mEACd,wBAAyB,8CACzB,iBAAkB,4DAClB,mBAAoB,2CACpB,WAAY,iBACZ,QAAS,8BACX,EAWO,SAAS,CAAW,CAAC,EAAmB,EAAsC,CACnF,GAAI,EAAK,SAAW,OAAQ,CAC1B,GAAI,EAAK,iBAAiB,EAAW,OAAO,EAC5C,MAAO,IACF,EACH,QAAS,aAAa,EAAK,4CAC7B,EAEF,IAAM,EAAU,EAAqB,EAAM,MAC3C,OAAO,IAAY,OAAY,EAAQ,IAAK,EAAO,QAAS,CAAQ,EAS/D,SAAS,CAAc,CAC5B,EACA,EACA,EACA,EACY,CACZ,IAAI,EACJ,GAAI,CACF,GAAO,GAAY,GAAa,EAAO,CAAI,EAC3C,MAAO,EAAK,CACZ,EAAU,CAAG,EACb,EAAM,EAAY,EAAO,CAAI,EAE/B,MAAO,CACL,KAAM,EAAI,KACV,QAAS,EAAI,WACT,EAAI,aAAe,OAAY,CAAE,WAAY,EAAI,UAAW,EAAI,CAAC,EACrE,UAAW,EAAI,SACjB,EASF,IAAM,EAAa,IAAI,QAEhB,SAAS,CAAkB,CAAC,EAAsB,EAA4B,CACnF,GAAI,IAAa,EAAU,EAAW,IAAI,EAAU,CAAQ,EAIvD,SAAS,CAAe,CAAC,EAA+B,CAC7D,OAAO,EAAW,IAAI,CAAK,GAAK",
|
|
8
|
-
"debugId": "50CFBD8E3EF3379764756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
package/dist/chunk-5zzhypmt.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{Kb as e}from"./chunk-3z9vhjd7.js";import{Vb as l,Xb as g,Zb as YZ,_b as UZ,ac as _Z}from"./chunk-9vsj0ksk.js";import{cc as WZ,fc as OZ,hc as HZ,ic as zZ,jc as q,kc as VZ,lc as KZ,nc as XZ}from"./chunk-g8kx549d.js";import{qc as I}from"./chunk-f1y8eggf.js";import{Cd as f,Hc as xZ,Lc as a,Md as o,Od as m,Sc as PZ,Sd as r,_d as y,ce as R,de as u,ee as d,nd as j,xd as CZ,yd as s}from"./chunk-6g20zec2.js";import{Be as BZ,Ce as i}from"./chunk-qpttgn4k.js";import{Uf as h,ig as t,kg as v,lg as w,mg as k}from"./chunk-cqnfp0n9.js";import{Bg as P,zg as GZ}from"./chunk-c7mp2kyp.js";import{Cg as B}from"./chunk-y6v0x869.js";import{Ig as NZ}from"./chunk-4t1pbt9p.js";import{wh as n}from"./chunk-vqa3agzw.js";import{Ch as JZ,Fh as b}from"./chunk-z5hrymvn.js";async function kZ(Z,$={}){try{if(!await Z.shouldRun.call(Z))return}catch(W){console.error(`Error evaluating shouldRun for cron job ${Z.name}:`,W);return}let G=MZ(Z.withoutOverlapping);if(!Z.onOneServer&&G===void 0){await p(Z);return}let H;try{if(H=$.locks?.()??yZ(),Z.onOneServer){let W=Math.floor(($.now??Date.now())/60000);if(!await H.store.advance(`gemi:cron-tick:${Z.name}`,W))return}}catch(W){console.error(`Could not claim the tick of cron job ${Z.name}; skipped it:`,W);return}if(G===void 0){await p(Z);return}try{await H.run(`gemi:cron:${Z.name}`,{ttl:G},(W)=>p(Z,W))}catch(W){if(W instanceof GZ)console.error(`Cron job ${Z.name} lost its withoutOverlapping lock while running: another tick may have started alongside it.`,W);else console.error(`Could not take the withoutOverlapping lock of cron job ${Z.name}:`,W)}}var ZZ=300000;function MZ(Z){if(!Z)return;if(Z===!0)return ZZ;return Z.expiresAfter??ZZ}var $Z;function yZ(){return $Z??=new P(new WZ),$Z}async function p(Z,$){try{await Z.onTick.call(Z)}catch(G){console.error(`Error executing cron job ${Z.name}:`,G)}try{await Z.callback.call(Z,$)}catch(G){console.error(`Error in cron job ${Z.name}:`,G)}try{await Z.onComplete.call(Z)}catch(G){console.error(`Error completing cron job ${Z.name}:`,G)}}class F{static token="scheduler";handles=[];resolved;inFlight=new Map;stopped=!1;locks;constructor(Z){this.resolved=Z.jobs}get jobs(){return[...this.resolved]}useJobs(Z){this.resolved=Z}useLocks(Z){this.locks=Z}start(Z=($)=>$()){if(this.stopped=!1,this.resolved.length===0)return;let $=globalThis.__gemiCronJobs??=new Map,G=new Map;for(let H of this.resolved){let W=new H;if(!W.name){console.error(`Cron job must have a name \u2014 ${H.name||"an anonymous class"} has none, so it is not scheduled. If this is a base class that other jobs extend, move it out of the discovered directory: \`abstract\` does not survive to runtime, so a class under app/cron is indistinguishable from a job.`);continue}if(!W.cron){console.error(`Cron job must have an expression. Job: ${W.name}`);continue}let z=G.get(W.name);if(z!==void 0){console.error(`Two cron jobs claim the name "${W.name}" \u2014 ${z} is scheduled and ${H.name||"an anonymous class"} is not. A name is the scheduler's key, so only one job can hold it. Rename one.`);continue}G.set(W.name,H.name||"an anonymous class"),$.get(W.name)?.stop();let Y=Bun.cron(W.cron,()=>{if(this.stopped)return;let V=this.locks;return this.track(W.name,()=>Z(()=>kZ(W,{locks:V})))});$.set(W.name,Y),this.handles.push(Y)}}stop(){this.stopped=!0;for(let Z of this.handles)Z.stop();this.handles=[]}async drain(Z=1/0){this.stop();let $,G=new Promise((H)=>{if(Z!==1/0)$=setTimeout(H,Z)});return await Promise.race([Promise.allSettled([...this.inFlight.values()].map((H)=>H.done)),G]),clearTimeout($),{unfinished:[...this.inFlight.values()].map((H)=>({...H.tick}))}}get running(){return this.inFlight.size}track(Z,$){let G={},H=Promise.resolve().then($).finally(()=>this.inFlight.delete(G));return this.inFlight.set(G,{tick:{name:Z,startedAt:new Date},done:H}),H}}import{isMainThread as wZ}from"worker_threads";var fZ=100,bZ=(Z)=>Math.min(fZ,Math.floor(Z/10));class gZ extends I{register(){this.app.singleton(q,()=>new q(this.app.config.get("queue",{}),{application:this.app})),this.app.singleton(P,()=>this.app.make(q).locks)}async boot(){let Z=this.app.config.get("queue",{});if(Z.jobs!==void 0)return;let{jobsDir:$}=B(HZ(),Z);this.app.make(q).useJobs(await YZ($))}async shutdown(Z){if(!this.app.resolved(q))return;let $=this.app.make(q);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running queued job(s).`);let G=Z?.timeoutMs,{unfinished:H}=await $.drain(G===void 0?1/0:Math.max(0,G-bZ(G)));if(H.length>0)return console.error("[gemi] Queued jobs still running at shutdown: "+H.map((W)=>`${W.name} (${W.id})`).join(", ")),{abandoned:!0}}}function T3(Z){if(mZ(Z),Bun.env.NODE_ENV!=="production")return hZ(Z);if(IZ(Z))return;if(!VZ())return;if(!Z.bound(q))return;Z.make(q).start()}function IZ(Z){let $=Z.config.get("queue",{}).driver??"memory";return $==="memory"||$ instanceof zZ}function vZ(Z){if(IZ(Z))return!0;if(typeof Z.config.get("queue",{}).driver!=="function"||!Z.bound(q))return!1;try{return!Z.make(q).durable}catch{return!1}}function mZ(Z){let $=process.env.GEMI_QUEUE_CLAIM;if($===void 0||XZ())return;if(process.env.ROOT_DIR===void 0||!wZ)return;if(KZ()){if(!vZ(Z))return;console.warn('[gemi] GEMI_QUEUE_CLAIM=off is ignored: the memory queue driver keeps jobs in the process that dispatched them, so no worker could run them. This server runs its own jobs. Use a driver other processes can read, such as "database", to hand jobs to `gemi queue:work`.')}else if($.trim().toLowerCase()!=="on")console.warn(`[gemi] GEMI_QUEUE_CLAIM="${$}" is not "off" or "on", so this server claims jobs. Set it to "off" to leave them to \`gemi queue:work\`.`)}function hZ(Z){let $=globalThis.__gemiDevQueue;if(!$)return;if(globalThis.__gemiDevQueue=void 0,$.stop(),Z.bound(q))Z.make(q).start()}class cZ extends I{register(){this.app.singleton(j,()=>new j(this.app.config.get("changeFeed",{}),{application:this.app}))}async shutdown(){if(!this.app.resolved(j))return;await this.app.make(j).close()}}class uZ extends I{register(){this.app.singleton(d,()=>new d(this.app.config.get("auth",{})))}}var pZ=100,dZ=(Z)=>Math.min(pZ,Math.floor(Z/10));class lZ extends I{register(){this.app.singleton(F,()=>new F(B(l(),this.app.config.get("schedule",{}))))}async boot(){let Z=this.app.make(F),$=this.app.config.get("schedule",{});if($.jobs===void 0){let{jobsDir:H}=B(l(),$);Z.useJobs(await UZ(H))}if(process.env.GEMI_NO_SCHEDULE==="1")return;let G=this.app;if(G.bound(P))Z.useLocks(()=>G.make(P));Z.start((H)=>JZ.run(G,H))}async shutdown(Z){if(!this.app.resolved(F))return;let $=this.app.make(F);if($.running>0)console.log(`[gemi] Shutting down: waiting for ${$.running} running cron tick(s).`);let G=Z?.timeoutMs,{unfinished:H}=await $.drain(G===void 0?1/0:Math.max(0,G-dZ(G)));if(H.length>0)return console.error("[gemi] Cron jobs still running at shutdown: "+H.map((W)=>`${W.name} (started ${W.startedAt.toISOString()})`).join(", ")),{abandoned:!0}}}class T extends CZ{modelName;constructor(Z="FeatureFlag"){super();this.modelName=Z}get available(){return BZ(this.modelName)}async load(){if(!this.available)throw new s(this.modelName);let Z=i(this.modelName);return await Z.asSystem(()=>Z.findMany())}async write(Z,$,G){if(!this.available)throw new s(this.modelName);let H=i(this.modelName),W=this.hasActorColumn(H),z={active:$};if(W)z.updatedBy=G.actor;return await H.asSystem(()=>H.upsert({where:{key:Z},create:{key:Z,...z},update:z})),{actorRecorded:W}}hasActorColumn(Z){let $=Z?.$schema?.fields?.updatedBy;return $!==void 0&&$.type==="String"}}function i3(Z){return Z}function qZ(){return{features:void 0,enabled:!0,source:new T,model:"FeatureFlag",ttl:30,context:void 0,onEvaluate:void 0,maxClientFlags:200}}class iZ extends I{register(){this.app.singleton(f,()=>{let Z=this.app.config.get("features",{}),$=B(qZ(),Z);if(Z?.model!==void 0&&Z?.source===void 0)$.source=new T(Z.model);else if(Z?.model!==void 0&&Z.source instanceof T&&Z.source.modelName!==Z.model)w.warning(`app/config/features.ts sets \`model: "${Z.model}"\` and a source reading "${Z.source.modelName}". The source wins; drop \`model\` to silence this.`);return new f($,(G)=>w.warning(G))})}async boot(){let Z=this.app.make(f);if(!Z.config.enabled)return;try{await Z.refresh()}catch($){w.error(`Could not warm the feature flag cache at boot. ${$ instanceof Error?$.message:String($)}`)}}}class sZ extends I{register(){this.app.singleton(n,()=>new n(this.app.config.get("mail",{})))}}function nZ(Z){return`listener:${Z}`}function QZ(Z,$){tZ(Z);let{maxAttempts:G,backoff:H,worker:W}=$,z=Z.event.name,Y=class extends OZ{maxAttempts=G;backoff=H;worker=W;async run(...V){let[K,J]=oZ(V),C=b(L).rehydrate(K,J);await new Z().handle(C)}onFail(V){console.error(`The listener ${Z.name} threw while handling ${z} off the queue. The queue will retry it up to ${G} times in total; nothing the dispatcher did is waiting on it.`,V)}onDeadletter(V){console.error(`The listener ${Z.name} failed every one of its ${G} attempts at ${z} and has been dead-lettered, so this side effect did not happen and will not be retried.`,V)}};return Object.defineProperty(Y,"name",{value:nZ(Z.name),writable:!0}),Y}function tZ(Z){if(Object.getOwnPropertyDescriptor(Z,"name")?.writable)return;throw Error(`The listener ${Z.name} sets \`queued = true\` but does not declare \`static name\`, so it would be registered with the queue `+"under its class name \u2014 which a production build renames while the "+`payload that names it does not. Add: static name = "${Z.name}";`)}function oZ(Z){return typeof Z[0]==="string"?Z:Z[0]}class L{static token="events";listeners={};events={};warnedFor=new Set;warnedAboutWaiting=new Set;config;constructor(Z={}){this.config=B(g(),Z),this.useListeners(this.config.listeners)}useListeners(Z){this.config.listeners=Z,this.listeners={},this.events={};let $=new Set;for(let G of Z){if(!G.event){console.error(`Listener ${G.name} does not declare \`static event\`, so there is no event name to register it under and its handle will never run. Add: static event = SomeEvent;`);continue}if($.has(G.name)){console.error(`Two event listeners are named "${G.name}" \u2014 the first is `+"registered and this one is not. A listener's name identifies it across the framework, so only one class can hold it. Rename one.");continue}$.add(G.name);let H=G.event.name,W=new G;this.events[H]??=G.event,(this.listeners[H]??=[]).push({listener:G,job:W.queued?QZ(G,W):null})}}get queuedListenerJobs(){return Object.values(this.listeners).flat().flatMap(({job:Z})=>Z?[Z]:[])}get registeredListeners(){return[...this.config.listeners]}dispatch(Z,$){if(this.deferToCommit(Z,$,!1))return;this.runListeners(Z,$)}async dispatchAndWait(Z,$){if(this.deferToCommit(Z,$,!0))return;return this.runListeners(Z,$)}deferToCommit(Z,$,G){let{name:H,afterCommit:W}=Z.constructor;if(!W)return!1;let z=NZ(()=>this.runListeners(Z,$));if(z&&G)this.warnNothingIsWaitedFor(H);return z}async runListeners(Z,$){let G=Z.constructor.name,H=this.listeners[G];if(!H?.length){this.warnNothingIsListening(G);return}for(let{listener:W,job:z}of H){if(z){this.enqueue(z,W,G,$);continue}try{await new W().handle(Z)}catch(Y){console.error(`The listener ${W.name} threw while handling ${G}. The `+"listeners after it still ran \u2014 a listener's failure is its own.",Y)}}}rehydrate(Z,$=[]){let G=this.events[Z];if(!G)throw Error(`Cannot rebuild the event "${Z}" that came off the queue: no registered listener binds to an event of that name, so there is no class to construct. A queued listener runs in an application that `+"discovered its own listeners \u2014 check that this process sees the "+`same app/listeners as the one that dispatched, and that the event declares \`static name = "${Z}"\`.`);return new G(...$)}enqueue(Z,$,G,H){if(H===void 0){console.error(`The listener ${$.name} is queued, but ${G} reached the dispatcher without its constructor arguments, so there is nothing for the queue to rebuild it from and the listener was skipped. Dispatch through ${G}.dispatch(...) or ${G}.dispatchAndWait(...), which carry them.`);return}let W=(z)=>console.error(`The listener ${$.name} could not be queued for ${G}, so it did not run and will not be retried. The listeners after it still ran.`,z);try{b(q).push(Z,JSON.stringify([G,H]),{reportFailure:!1}).catch(W)}catch(z){W(z)}}warnNothingIsWaitedFor(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedAboutWaiting.has(Z))return;this.warnedAboutWaiting.add(Z),console.warn(`[gemi] ${Z}.dispatchAndWait() was called inside a transaction and ${Z} declares \`static afterCommit = true\`, so it resolved `+"immediately with no listener having run \u2014 they run when the "+"transaction commits. Await the transaction instead, or drop afterCommit if the caller needs the side effects first. Development only.")}warnNothingIsListening(Z){if(Bun.env.NODE_ENV==="production")return;if(this.warnedFor.has(Z))return;this.warnedFor.add(Z),console.warn(`[gemi] ${Z} was dispatched and nothing is listening for it. A listener registers under the name its event declares, so check that app/listeners holds a Listener with \`static event = ${Z}\` and that ${Z} declares \`static name = "${Z}"\`. Development only.`)}}class rZ extends I{register(){this.app.singleton(L,()=>new L(this.app.config.get("events",{})))}async boot(){let Z=this.app.make(L),$=this.app.config.get("events",{});if($.listeners===void 0){let{listenersDir:W}=B(g(),$);Z.useListeners(await _Z(W))}let G=Z.queuedListenerJobs;if(G.length===0)return;let H=this.app.make(q);for(let W of G)H.registerJob(W)}}class aZ extends I{register(){this.app.singleton(t,()=>new t(this.app.config.get("filesystem",{})))}}class eZ extends I{register(){this.app.singleton(o,()=>new o(this.app.config.get("image",{})))}}class Z3 extends I{register(){this.app.instance(r,new r)}}function M4(Z){return Z}function AZ(){return{maxFileSize:10485760,onLogFileClosed:()=>{},onLogCreated:()=>{}}}class $3 extends I{register(){this.app.singleton(v,()=>new v(B(AZ(),this.app.config.get("log",{}))))}async boot(){if(!process.env.ROOT_DIR)return;await this.app.make(v).boot()}}class G3 extends I{register(){this.app.singleton(m,()=>new m(B(xZ(),this.app.config.get("middleware",{}))))}boot(){this.app.make(m).assertGlobalMiddleware()}}function l4(Z){return Z}function FZ(){return{driver:new PZ,limit:1000,window:60}}class W3 extends I{register(){this.app.singleton(a,()=>new a(B(FZ(),this.app.config.get("ratelimiter",{}))))}}function a4(Z){return Z}function LZ(){return{url:process.env.REDIS_URL,options:void 0}}class H3 extends I{register(){this.app.singleton(k,()=>new k(B(LZ(),this.app.config.get("redis",{}))))}shutdown(){if(!this.app.resolved(k))return;this.app.make(k).close()}}class N extends e{status;constructor(Z,$){super(Z);this.status=$;this.name="McpToolError"}}var z3=/^[A-Za-z0-9_-]{1,64}$/,V3=/:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g,SZ=4000,E=1e5,K3=/^(text\/|application\/([\w.+-]*\+)?(json|xml))/i;class M{router;dispatcher;static token="router.mcp";plans=new Map;constructor(Z,$){this.router=Z;this.dispatcher=$;for(let[G,H]of Object.entries(Z.routes??{}))this.plans.set(G,this.plan(G,H))}list(Z,$){return TZ(Z),this.descriptors($)}descriptors(Z){let $=[];for(let{descriptor:G}of this.plans.values()){if(Z?.names&&!Z.names.includes(G.name))continue;if(Z?.tags&&!G.tags.some((H)=>Z.tags.includes(H)))continue;$.push(G)}return $}async execute(Z,$,G,H){TZ(Z);let W=this.plans.get($);if(!W)throw Error(`McpRegistry: there is no tool named "${$}".`);let{descriptor:z}=W,Y=z.inputSchema.safeParse(G);if(Y.ok===!1)throw new N(`Invalid arguments for "${$}": ${Y.errors.join(", ")}`);let V=Y.value,K={caller:Z,req:Z.req,tool:z,input:V,ctx:H},J=await this.fillPath(W,K),{path:C}=this.dispatcher.getRouteHandlerAndParams(new Request(`http://gemi.internal/api${J}`));if(C!==z.url)throw new N(`"${$}" has nothing at ${J}.`,404);let U={};for(let _ of W.jsonKeys)if(V[_]!==void 0)U[_]=V[_];let Q,A="";if(W.fileFields.length>0)Q=await this.formData(W,U,K);else if(z.method==="GET")A=U3(U);else if(W.jsonKeys.length>0)Q=U;let D=this.router.credentials?await this.bind(W,"the credentials",()=>this.router.credentials(K)):void 0,S;try{let _=A?`${J}?${A}`:J;S=D?await this.dispatcher.dispatchAs(Z.req,z.method,_,Q,{credentials:D}):await this.dispatcher.dispatchAs(Z.req,z.method,_,Q)}catch(_){throw console.error(`[gemi/mcp] Dispatching "${$}" failed:`,_),new N(`"${$}" failed on the server.`,500)}if(!W.result&&!z.outputSchema)return await DZ($,S);return await this.project(W,K,S)}plan(Z,$){if(!z3.test(Z))throw Error(`McpRouter: "${Z}" 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 G=`McpRouter: "${Z}" (${$?.method} ${$?.url})`;if(!($ instanceof Object)||$.__internal_brand!=="McpRoute")throw Error(`McpRouter: "${Z}" is not a route. Declare it with this.fromApiRoute(...).`);let{method:H,url:W,meta:z}=$;if(W.startsWith("/__gemi__"))throw Error(`${G} is a framework route, not an app route.`);let Y=this.dispatcher.flatRoutes[W]?.[H];if(!Y)throw Error(`${G} names no route of this app. Expose a route by the path and verb it is mounted at.`);let V=[...W.matchAll(V3)].map(([,X,O])=>({name:X,modifier:O})),K=z.params??{};for(let X of V)if(!(X.name in K))throw Error(`${G}: the path param "${X.name}" must be bound or declared "input".`);for(let[X,O]of Object.entries(K)){if(!V.some((x)=>x.name===X))throw Error(`${G}: "${X}" in params is not a param of the url.`);if(RZ(O))X3(G,`params.${X}`,O);else EZ(G,`params.${X}`,O)}let J=[],C;if(z.input){if(C=z.input.toJSONSchema(),C.type!=="object"||!C.properties)throw Error(`${G}: input must be an s.object(...), the fields of the request body.`);J=Object.keys(C.properties)}let U=Object.entries(z.files??{}).map(([X,O])=>({name:X,binder:O}));for(let X of U)EZ(G,`files.${X.name}`,X.binder);if(z.result!==void 0&&typeof z.result!=="function")throw Error(`${G}: result must be a function.`);if(z.output!==void 0&&typeof z.output?.safeParse!=="function")throw Error(`${G}: output must be a schema, built with s.`);if(U.length>0&&H==="GET")throw Error(`${G}: a GET has no body to carry a file.`);let Q={},A=(X,O)=>{if(X in Q)throw Error(`${G}: "${X}" is the input key of two params or files, and the model can only send one.`);Q[X]=O},D=new Map,S=new Map;for(let X of V){let O=K[X.name];if(O==="input"){let x=h.string().describe(`The ":${X.name}" segment of the url.`);A(X.name,X.modifier==="?"||X.modifier==="*"?x.optional():x)}else if(RZ(O)){let x=O.as??X.name;A(x,O.input),S.set(X.name,{key:x,param:O})}else D.set(X.name,O)}for(let X of U)if(X.binder==="input")A(X.name,h.string().describe("The id of an attachment the user uploaded or a tool produced (gemi_att_\u2026), whose file is sent as this field."));let _=[...Object.keys(Q),...U.map((X)=>X.name)];for(let X of new Set(_))if(J.includes(X))throw Error(`${G}: "${X}" is both an input field and a param or file, and the model can only send one.`);return{descriptor:Object.freeze({name:Z,description:z.description,method:H,url:W,inputSchema:I3(z.input,C,h.object(Q)),...z.output?{outputSchema:z.output}:{},annotations:Y3(H),tags:Object.freeze([...z.tags??[]]),requiresApproval:z.requiresApproval===!0,...Y.source?{source:Y.source}:{}}),params:V,paramBinders:D,modelParams:S,fileFields:U,jsonKeys:J,...z.result?{result:z.result}:{}}}async fillPath(Z,$){let{input:G,req:H}=$,W=Z.descriptor.url;for(let z of Z.params){let Y=Z.paramBinders.get(z.name),V=Z.modelParams.get(z.name),K;if(Y||V){if(K=V?await this.bind(Z,`the param "${z.name}"`,()=>V.param.bind(G[V.key],H,$),{refusals:!0}):await this.bind(Z,`the param "${z.name}"`,()=>Y(H,$)),K===void 0||K===null||K==="")throw console.error(`[gemi/mcp] The ${V?"resolver":"binder"} for "${z.name}" of "${Z.descriptor.name}" returned ${JSON.stringify(K)}.`),new N(`"${Z.descriptor.name}" failed on the server.`,500)}else K=G[z.name];let J=`:${z.name}${z.modifier}`;if(K===void 0||K===null||K===""){if(z.modifier!=="?"&&z.modifier!=="*")throw new N(`"${z.name}" of "${Z.descriptor.name}" must not be empty.`);W=W.replace(`/${J}`,"");continue}W=W.replace(J,J3(Z.descriptor.name,z,String(K)))}return W}async formData(Z,$,G){let{input:H,ctx:W}=G,z=Z.descriptor.name;if(!W)throw Error(`McpRegistry: "${z}" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`);let Y=new FormData;for(let[V,K]of Object.entries($))jZ(Y,V,K);for(let V of Z.fileFields){let K=V.binder==="input"?H[V.name]:await this.bind(Z,`the file "${V.name}"`,()=>V.binder(W,G));if(typeof K!=="string"||K==="")throw new N(`"${z}" needs a file for "${V.name}", and this turn has none. Ask the user to attach one.`);Y.append(V.name,await W.attachments.file(K))}return Y}async project(Z,$,G){let{name:H,outputSchema:W}=Z.descriptor;if(G.status<200||G.status>=300)return await DZ(H,G);let z=(U,Q)=>{if(Q===void 0)console.error(`[gemi/mcp] "${H}" ${U}`);else console.error(`[gemi/mcp] "${H}" ${U}`,Q);return new N(`"${H}" failed on the server.`,500)},Y=G.headers.get("Content-Type"),V=await G.text(),K=null;if(V!=="")try{K=JSON.parse(V)}catch{throw z(`answered ${Y??"a body with no content-type"} that is not JSON, and its result or output needs JSON.`)}let J=K;if(Z.result)try{J=await Z.result(K,$)}catch(U){throw z("failed in its result projection:",U)}if(W){let U=W.safeParse(J);if(U.ok===!1)throw z(`answered what its output schema refuses: ${U.errors.join(", ")}`);J=U.value}if(J===void 0)return null;let C=JSON.stringify(J);if(C!==void 0&&C.length>E)return`${C.slice(0,E)}\u2026 [cut at ${E} of ${C.length} characters]`;return J}async bind(Z,$,G,H={}){try{return await G()}catch(W){if(H.refusals&&W instanceof e)throw W;throw console.error(`[gemi/mcp] Binding ${$} of "${Z.descriptor.name}" failed:`,W),new N(`"${Z.descriptor.name}" failed on the server.`,500)}}}function TZ(Z){if(Z?.kind!=="local")throw Error("McpRegistry: only local callers are implemented. A remote caller needs a bearer-token resolver, which v1 does not have \u2014 and until it does there is no remote surface to call from.")}function EZ(Z,$,G){if(G!=="input"&&typeof G!=="function")throw Error(`${Z}: ${$} must be a function or "input".`)}function RZ(Z){return Z instanceof Object&&Z.__internal_brand==="McpModelParam"}function X3(Z,$,G){if(G.as!==void 0&&(typeof G.as!=="string"||G.as===""))throw Error(`${Z}: ${$}.as must be a non-empty string.`);let H=G.input;if(typeof H?.safeParse!=="function"||typeof H.toJSONSchema!=="function")throw Error(`${Z}: ${$}.input must be a schema, built with s.`);if(typeof G.bind!=="function")throw Error(`${Z}: ${$}.bind must be a function.`)}function Y3(Z){switch(Z){case"GET":return Object.freeze({readOnlyHint:!0});case"DELETE":return Object.freeze({destructiveHint:!0});case"PUT":return Object.freeze({idempotentHint:!0});default:return Object.freeze({})}}function I3(Z,$,G){let H=G.toJSONSchema(),W={...$??{},type:"object",properties:{...$?.properties??{},...H.properties},required:[...$?.required??[],...H.required??[]],additionalProperties:!1},z=(Y)=>{if(typeof Y!=="object"||Y===null||Array.isArray(Y))return{ok:!1,errors:["expected an object"]};let V=[],K={};if(Z){let C=Z.safeParse(Y);if(C.ok===!1)V.push(...C.errors);else K={...C.value}}let J=G.safeParse(Y);if(J.ok===!1)V.push(...J.errors);else K={...K,...J.value};return V.length>0?{ok:!1,errors:V}:{ok:!0,value:K}};return{toJSONSchema:()=>W,safeParse:z,parse(Y){let V=z(Y);if(V.ok===!1)throw Error(V.errors.join(", "));return V.value}}}function J3(Z,$,G){let H=$.modifier==="*"||$.modifier==="+"?G.split("/"):[G];if(H.some((W)=>W==="."||W===".."))throw new N(`"${G}" is not a valid value for "${$.name}" of "${Z}".`);return H.map(encodeURIComponent).join("/")}function jZ(Z,$,G){if(G===void 0||G===null)return;if(Array.isArray(G)){for(let H of G)jZ(Z,$,H);return}Z.append($,typeof G==="object"?JSON.stringify(G):String(G))}function U3(Z){let $=new URLSearchParams,G=(H,W)=>{if(W===void 0||W===null)return;if(Array.isArray(W)){for(let z of W)G(H,z);return}$.append(H,typeof W==="object"?JSON.stringify(W):String(W))};for(let[H,W]of Object.entries(Z))G(H,W);return $.toString()}async function DZ(Z,$){if($.status>=200&&$.status<300){let H=$.headers.get("Content-Type");if(H&&!K3.test(H)){let z=(await $.arrayBuffer()).byteLength;return`"${Z}" answered with ${z} bytes of ${H}, which is not shown.`}let W=await $.text();if(W==="")return null;if(W.length>E)return`${W.slice(0,E)}\u2026 [cut at ${E} of ${W.length} characters]`;try{return JSON.parse(W)}catch{return W}}let G=await $.text();if($.status>=400&&$.status<500){let H=G.length>SZ?`${G.slice(0,SZ)}\u2026`:G;throw new N(`"${Z}" was refused with ${$.status}${H?`: ${H}`:"."}`,$.status)}if($.status<500)console.error(`[gemi/mcp] "${Z}" answered ${$.status}${$.headers.get("Location")?` to ${$.headers.get("Location")}`:""}, which a tool call cannot follow.`);throw new N(`"${Z}" failed on the server.`,$.status)}function c(Z,$){let G=Z.get($);if(!G)throw Error(`Missing "${$}" configuration. Route dispatching needs a root router \u2014 declare it in "app/config/route.ts".`);return G}class B3 extends I{register(){this.app.singleton(R,()=>new R(c(this.app.config,"route.api"))),this.app.singleton(y,()=>new y(c(this.app.config,"route.view"))),this.app.singleton(u,()=>new u({api:this.app.make(R),view:this.app.make(y)},{api:c(this.app.config,"route.api"),view:c(this.app.config,"route.view"),domains:this.app.config.get("route.domains")})),this.app.singleton(M,()=>{let Z=this.app.config.get("route.mcp");if(!Z?.router)throw Error('No MCP router is configured. Declare one as `mcp: { router }` in "app/config/route.ts".');return new M(new Z.router,this.app.make(R))})}boot(){if(this.app.make(R),this.app.make(y),this.app.make(u),this.app.config.get("route.mcp"))this.app.make(M)}}
|
|
3
|
-
export{F as hb,gZ as ib,T3 as jb,cZ as kb,uZ as lb,lZ as mb,T as nb,i3 as ob,qZ as pb,iZ as qb,sZ as rb,L as sb,rZ as tb,aZ as ub,eZ as vb,Z3 as wb,M4 as xb,AZ as yb,$3 as zb,G3 as Ab,l4 as Bb,FZ as Cb,W3 as Db,a4 as Eb,LZ as Fb,H3 as Gb,N as Hb,M as Ib,B3 as Jb};
|
|
4
|
-
|
|
5
|
-
//# debugId=99432915D07BB43864756E2164756E21
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../services/cron/Scheduler.ts", "../services/queue/QueueServiceProvider.ts", "../services/change-feed/ChangeFeedServiceProvider.ts", "../auth/AuthServiceProvider.ts", "../services/cron/ScheduleServiceProvider.ts", "../services/features/sources/DatabaseFeatureFlagSource.ts", "../services/features/config.ts", "../services/features/FeaturesServiceProvider.ts", "../services/email/MailServiceProvider.ts", "../services/events/listenerJob.ts", "../services/events/EventManager.ts", "../services/events/EventServiceProvider.ts", "../services/file-storage/FilesystemServiceProvider.ts", "../services/image-optimization/ImageServiceProvider.ts", "../services/kernel-id/KernelIdServiceProvider.ts", "../services/logging/config.ts", "../services/logging/LogServiceProvider.ts", "../services/middleware/MiddlewareServiceProvider.ts", "../services/rate-limiter/config.ts", "../services/rate-limiter/RateLimiterServiceProvider.ts", "../services/redis/config.ts", "../services/redis/RedisServiceProvider.ts", "../services/mcp/McpRegistry.ts", "../services/router/RouteServiceProvider.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { type HeldLock, LockManager } from \"../lock/LockManager\";\nimport { LockLostError } from \"../lock/LockStore\";\nimport { MemoryLockStore } from \"../lock/MemoryLockStore\";\nimport type { CronJob } from \"./CronJob\";\nimport type { ScheduleConfig } from \"./config\";\n\n// The handle the in-process `Bun.cron(schedule, callback)` overload returns\n// (has `.stop()`). `ReturnType<typeof Bun.cron>` can't be used — it resolves to\n// the module-path overload's `Promise<void>` instead.\ntype BunCronHandle = Bun.CronJob;\n\n// Runs a tick inside whatever async context the framework needs, so a job body\n// can resolve services exactly like a request handler can.\nexport type ScheduleRunner = <T>(cb: () => T | Promise<T>) => Promise<T> | T;\n\ndeclare global {\n // Registered cron jobs keyed by name, kept on `globalThis` so it survives\n // `bun --hot` reloads. Re-registering a name stops the previous schedule\n // first, so a reload *updates* the job in place instead of stacking a\n // duplicate. (Bun also stops all in-process cron jobs before re-evaluating on\n // hot reload; this makes the intent explicit and covers any re-construction.)\n var __gemiCronJobs: Map<string, BunCronHandle> | undefined;\n}\n\n/**\n * One tick of one job: the gate, then the three hooks in order.\n *\n * Named and exported rather than left inline in `start()` because otherwise the\n * only way to reach it is to wait — the tightest schedule `Bun.cron` accepts is\n * once a minute, which is not a thing a test can honestly sit through. What a\n * caller gives up by calling this directly is Bun's clock; what it keeps is\n * everything the scheduler puts between the clock and the job, which is the\n * part that has behaviour.\n *\n * The three hooks each catch and log, so one throwing does not stop the next —\n * `onComplete` in particular runs whether `callback` returned or threw, closer\n * to a `finally` than to a success handler.\n */\nexport async function runTick(job: CronJob, options: TickOptions = {}): Promise<void> {\n try {\n // Plain truthiness, not `!== false`. The sloppy gate an app writes is\n // `if (!isProduction) return false;`, which returns `undefined` on the path\n // that was meant to run — and between the two readings of that, the one\n // that skips a recurring tick is recoverable and the one that mails a\n // customer from a developer's laptop is not. The declared return type makes\n // that method a compile error anyway, so the type system catches it while\n // it is still being written and the runtime stays on the safe side.\n if (!(await job.shouldRun.call(job))) {\n return;\n }\n } catch (error) {\n // Fail closed, for the same reason. A gate that throws has told us nothing\n // about whether the job may run, and the framework already refuses to\n // schedule a job whose name or expression it cannot read rather than run\n // something half-declared. The throw is logged, so this is loud; the missed\n // tick comes back on the next one.\n console.error(\n `Error evaluating shouldRun for cron job ${job.name}:`,\n error,\n );\n return;\n }\n\n const overlap = overlapLease(job.withoutOverlapping);\n if (!job.onOneServer && overlap === undefined) {\n await runHooks(job);\n return;\n }\n\n // Fail closed, like the gate: a tick that cannot find out whether it may\n // run is skipped, and the next one tries again.\n let locks: LockManager;\n try {\n locks = options.locks?.() ?? processLocks();\n if (job.onOneServer) {\n const minute = Math.floor((options.now ?? Date.now()) / 60_000);\n if (!(await locks.store.advance(`gemi:cron-tick:${job.name}`, minute))) return;\n }\n } catch (error) {\n console.error(`Could not claim the tick of cron job ${job.name}; skipped it:`, error);\n return;\n }\n if (overlap === undefined) {\n await runHooks(job);\n return;\n }\n\n try {\n await locks.run(`gemi:cron:${job.name}`, { ttl: overlap }, (lock) => runHooks(job, lock));\n } catch (error) {\n if (error instanceof LockLostError) {\n console.error(\n `Cron job ${job.name} lost its withoutOverlapping lock while running: ` +\n `another tick may have started alongside it.`,\n error,\n );\n } else {\n console.error(`Could not take the withoutOverlapping lock of cron job ${job.name}:`, error);\n }\n }\n}\n\n/** What `runTick` needs beyond the job. */\nexport interface TickOptions {\n /**\n * The locks `withoutOverlapping` and `onOneServer` take. The scheduler\n * passes the queue's, so they hold across instances sharing its storage;\n * without one they hold within this process only.\n */\n locks?: () => LockManager;\n /** The tick's time, for `onOneServer`. Default now. */\n now?: number;\n}\n\nconst DEFAULT_OVERLAP_LEASE = 5 * 60_000;\n\nfunction overlapLease(option: CronJob[\"withoutOverlapping\"]): number | undefined {\n if (!option) return undefined;\n if (option === true) return DEFAULT_OVERLAP_LEASE;\n return option.expiresAfter ?? DEFAULT_OVERLAP_LEASE;\n}\n\nlet fallbackLocks: LockManager | undefined;\nfunction processLocks() {\n fallbackLocks ??= new LockManager(new MemoryLockStore());\n return fallbackLocks;\n}\n\nasync function runHooks(job: CronJob, lock?: HeldLock) {\n try {\n await job.onTick.call(job);\n } catch (error) {\n console.error(`Error executing cron job ${job.name}:`, error);\n }\n try {\n await job.callback.call(job, lock);\n } catch (error) {\n console.error(`Error in cron job ${job.name}:`, error);\n }\n try {\n await job.onComplete.call(job);\n } catch (error) {\n console.error(`Error completing cron job ${job.name}:`, error);\n }\n}\n\n/** A tick `drain` could not wait out. */\nexport interface UnfinishedTick {\n /** The job's `name`. */\n name: string;\n /**\n * When this tick started. A job whose tick outlasts its own interval has\n * two ticks running at once, and the name alone would list it twice with\n * nothing to tell the two apart.\n */\n startedAt: Date;\n}\n\n/** What `drain` could not wait out. */\nexport interface ScheduleDrainResult {\n /**\n * Ticks still running when the timeout elapsed. They keep running — a tick\n * has no way to be told to stop — until they finish or the process exits.\n */\n unfinished: UnfinishedTick[];\n}\n\nexport class Scheduler {\n static token = \"scheduler\";\n\n private handles: BunCronHandle[] = [];\n private resolved: Array<new () => CronJob>;\n\n /**\n * Every tick this scheduler has started and not seen settle, keyed by an\n * object of its own so two overlapping ticks of one job are two entries.\n * Without this there is nothing for a shutdown to wait on, and a tick the\n * process exits under is cut off halfway through its `callback`.\n */\n private readonly inFlight = new Map<object, { tick: UnfinishedTick; done: Promise<unknown> }>();\n\n /**\n * Set by `stop()`, cleared by `start()`. `handle.stop()` is what keeps Bun\n * from firing again, and this is the second lock on the same door: a\n * callback Bun had already dispatched when `stop()` ran, or a handle some\n * other scheduler left in the hot-reload registry, still finds nothing to\n * start once the drain has begun.\n */\n private stopped = false;\n\n private locks: (() => LockManager) | undefined;\n\n constructor(config: Required<ScheduleConfig>) {\n this.resolved = config.jobs;\n }\n\n /**\n * The jobs this scheduler will run, or ran — declared in the config slice or\n * discovered under `app/cron`, whichever the slice asked for.\n *\n * #323 turned an application's schedule from an array it could import into a\n * directory it cannot, and an app that had a test walking that array —\n * asserting every report has an owner, that no two share an expression — was\n * about to lose it. This is where that test goes instead, and it is the\n * better question anyway: the config slice says what was asked for, this says\n * what the scheduler took.\n *\n * It lists what `start()` iterates, not what `Bun.cron` accepted. A job with\n * no name, no expression, or a name another job already claimed is logged and\n * skipped, and still appears here — a test walking this should see the\n * problem rather than have it tidied away.\n *\n * A copy, so that walk cannot edit the set the scheduler is running from.\n */\n get jobs(): ReadonlyArray<new () => CronJob> {\n return [...this.resolved];\n }\n\n /**\n * Replaces the job set, for the provider to hand over what it found under\n * `app/cron` before it calls `start()`.\n *\n * The scheduler is constructed in `register()`, where reading a directory and\n * importing what is in it is not allowed to happen — that phase is\n * synchronous and resolves nothing. So it is built from whatever the slice\n * declared and corrected in `boot()`, one line before it starts. Calling this\n * after `start()` changes what `jobs` reports and nothing else; the schedules\n * are already registered.\n */\n useJobs(jobs: Array<new () => CronJob>) {\n this.resolved = jobs;\n }\n\n /**\n * Where `withoutOverlapping` and `onOneServer` take their locks. The\n * provider hands over the queue's, resolved only when a tick needs them.\n */\n useLocks(locks: () => LockManager) {\n this.locks = locks;\n }\n\n /**\n * Schedules every configured job. Called from `ScheduleServiceProvider.boot()`\n * — never from the constructor — so a job body may resolve any service the\n * container holds by the time it first ticks. That ordering is what replaced\n * the old `kernel.waitForBoot()` handshake.\n */\n start(run: ScheduleRunner = (cb) => cb()) {\n this.stopped = false;\n if (this.resolved.length === 0) {\n return;\n }\n\n const registry = (globalThis.__gemiCronJobs ??= new Map());\n\n // Names claimed during *this* pass, which is a different question from what\n // `registry` holds. `registry` spans hot reloads and is supposed to already\n // contain the previous reload's handle under the same name; this starts\n // empty every time and is how a name claimed twice in one schedule is told\n // apart from the same job coming back after an edit.\n const claimed = new Map<string, string>();\n\n for (const Job of this.resolved) {\n const job = new Job();\n if (!job.name) {\n // Named by its class, because under discovery nobody wrote this job\n // down anywhere and `JSON.stringify(job)` on a nameless one prints\n // `{}`. The overwhelmingly common cause is the second sentence: a\n // shared base class sitting in the walked directory. `abstract` is\n // erased before this code runs, so there is nothing here that could\n // tell a base class from a job — only the message can.\n console.error(\n `Cron job must have a name — ${Job.name || \"an anonymous class\"} ` +\n `has none, so it is not scheduled. If this is a base class that ` +\n `other jobs extend, move it out of the discovered directory: ` +\n `\\`abstract\\` does not survive to runtime, so a class under ` +\n `app/cron is indistinguishable from a job.`,\n );\n continue;\n }\n if (!job.cron) {\n console.error(`Cron job must have an expression. Job: ${job.name}`);\n continue;\n }\n const taken = claimed.get(job.name);\n if (taken !== undefined) {\n // Refusing the second one, rather than letting it through to the\n // `registry.get(...).stop()` below — which is what used to happen, and\n // it stopped the *first* job's schedule while leaving both classes in\n // `jobs`. That is the failure #323 exists to remove wearing a disguise:\n // a report that silently stops arriving, and a test walking `jobs` that\n // sees two healthy entries and passes.\n console.error(\n `Two cron jobs claim the name \"${job.name}\" — ${taken} is ` +\n `scheduled and ${Job.name || \"an anonymous class\"} is not. A name ` +\n `is the scheduler's key, so only one job can hold it. Rename one.`,\n );\n continue;\n }\n claimed.set(job.name, Job.name || \"an anonymous class\");\n\n // Update in place: stop any schedule previously registered under this name\n // before re-registering, so a hot reload replaces rather than duplicates.\n registry.get(job.name)?.stop();\n\n // Inside `run`, gate included: a `shouldRun` that resolves a service to\n // decide is the ordinary case, and evaluating it out here would leave it\n // without the application context every other line of the tick has.\n const handle = Bun.cron(job.cron, () => {\n if (this.stopped) return;\n const locks = this.locks;\n return this.track(job.name, () => run(() => runTick(job, { locks })));\n });\n\n registry.set(job.name, handle);\n this.handles.push(handle);\n }\n }\n\n /**\n * Stops every schedule, so no new tick starts. A tick already running is\n * left alone — `drain` is how to wait for it.\n */\n stop() {\n this.stopped = true;\n for (const handle of this.handles) {\n handle.stop();\n }\n this.handles = [];\n }\n\n /**\n * Stops every schedule, waits up to `timeoutMs` for the ticks already\n * running, and reports the ones that did not finish.\n *\n * Stopping comes first, so a tick that falls due while this waits does not\n * start: a drain that kept the clock running would never be sure it had\n * waited for everything. Nothing is cancelled — a job has no way to be told\n * — so an unfinished tick keeps running after this resolves, until it ends\n * or the process does.\n *\n * A later `start()` schedules again.\n */\n async drain(timeoutMs = Infinity): Promise<ScheduleDrainResult> {\n this.stop();\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<void>((resolve) => {\n if (timeoutMs !== Infinity) timer = setTimeout(resolve, timeoutMs);\n });\n await Promise.race([\n Promise.allSettled([...this.inFlight.values()].map((e) => e.done)),\n deadline,\n ]);\n clearTimeout(timer);\n\n return {\n unfinished: [...this.inFlight.values()].map((e) => ({ ...e.tick })),\n };\n }\n\n /** How many ticks this scheduler is running right now. */\n get running(): number {\n return this.inFlight.size;\n }\n\n private track(name: string, tick: () => unknown): Promise<unknown> {\n const key = {};\n // `Promise.resolve().then(...)` rather than calling `tick` directly, so a\n // runner that throws synchronously lands in the same promise as one that\n // rejects, and the entry below is removed either way.\n const done = Promise.resolve()\n .then(tick)\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, { tick: { name, startedAt: new Date() }, done });\n return done;\n }\n}\n",
|
|
6
|
-
"import { isMainThread } from \"node:worker_threads\";\n\nimport type { Application } from \"../../foundation/Application\";\nimport { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { discoverJobs } from \"../discovery\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { queueConfigDefaults, type QueueConfig } from \"./config\";\nimport { LockManager } from \"../lock/LockManager\";\nimport { MemoryQueueDriver } from \"./MemoryQueueDriver\";\nimport {\n claimingTurnedOff,\n claimsInThisProcess,\n isQueueWorker,\n QueueManager,\n} from \"./QueueManager\";\n\n/**\n * How far inside the provider deadline the drain stops, so there is time left\n * to write the list of jobs it abandoned before `Application` stops waiting\n * for this provider and the process exits. A tenth of the budget, capped, so\n * that a deadline of a few milliseconds still spends most of itself waiting\n * rather than handing the whole of it to the margin.\n */\nconst REPORT_MARGIN_MS = 100;\n\nconst reportMargin = (timeoutMs: number) =>\n Math.min(REPORT_MARGIN_MS, Math.floor(timeoutMs / 10));\n\nexport class QueueServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n QueueManager,\n () =>\n new QueueManager(this.app.config.get<QueueConfig>(\"queue\", {}), {\n application: this.app,\n }),\n );\n // The `Lock` facade's binding: the queue's locks, over its storage.\n this.app.singleton(LockManager, () => this.app.make(QueueManager).locks);\n }\n\n /**\n * Fills in the job registry when the app did not declare one.\n *\n * ### Why the raw slice and not the manager's config\n *\n * The decision here is whether the app said anything, and by the time the\n * manager holds a config it can no longer tell: `withDefaults` treats an\n * absent key and an `undefined` one alike and substitutes the default `[]`,\n * which is the same value an app writes when it means \"no jobs, and I mean\n * it\". Reading the slice before defaults are applied is the only place the\n * difference still exists.\n *\n * So: `jobs` present, including `jobs: []`, is used verbatim and no directory\n * is read. Absent or `undefined`, the classes under `jobsDir` are.\n *\n * ### Why phase two\n *\n * Discovery imports every file it walks, which is asynchronous, and\n * `register()` is not. It also has to happen before the first dispatch rather\n * than before the first tick — there is no equivalent of the scheduler's\n * `start()` to hang it off, so the registry has to be complete by the end of\n * boot.\n */\n async boot() {\n const slice = this.app.config.get<QueueConfig>(\"queue\", {});\n if (slice.jobs !== undefined) return;\n\n const { jobsDir } = withDefaults(queueConfigDefaults(), slice);\n this.app.make(QueueManager).useJobs(await discoverJobs(jobsDir));\n }\n\n /**\n * Stops claiming and waits for the jobs this process is running, on the way\n * out of a server told to stop. Runs after the request drain, and before the\n * database provider's, so the jobs still have their connection.\n *\n * A job still running at the deadline is abandoned with the process. With\n * the memory driver that job is lost; with one that outlives the process it\n * is claimed again elsewhere once its lease runs out. With such a driver\n * claiming already stopped when the signal arrived, so the jobs here are\n * only the ones that were running then; the memory driver kept claiming\n * until now, so a job dispatched by a request still draining ran here too.\n *\n * The drain is bounded by a little less than what is left of the shared\n * provider deadline, so that this is the thing that names the jobs it gave\n * up on. Draining without a timeout of its own read better but could not\n * work: `drain()` then resolves only once every job has finished, by which\n * point nothing is unfinished and the list below is always empty — and the\n * operator who needed it got `Application`'s generic \"did not finish within\n * the provider shutdown deadline\" instead, which names no job at all. *\n * A drain that left jobs running resolves `{ abandoned: true }`, so\n * `Application` reports this provider in `timedOut` and the process exits 1:\n * the jobs are named in the log, and the exit code shows the stop was not\n * clean (#580).\n */\n async shutdown(options?: { timeoutMs: number }) {\n // Resolving the manager now would build a driver only to stop it.\n if (!this.app.resolved(QueueManager)) return;\n const queue = this.app.make(QueueManager);\n if (queue.running > 0) {\n console.log(\n `[gemi] Shutting down: waiting for ${queue.running} running queued job(s).`,\n );\n }\n const budget = options?.timeoutMs;\n const { unfinished } = await queue.drain(\n budget === undefined ? Infinity : Math.max(0, budget - reportMargin(budget)),\n );\n if (unfinished.length > 0) {\n console.error(\n `[gemi] Queued jobs still running at shutdown: ` +\n unfinished.map((job) => `${job.name} (${job.id})`).join(\", \"),\n );\n return { abandoned: true };\n }\n }\n}\n\n/**\n * Starts claiming once the application has booted, in a production server\n * whose driver keeps jobs outside the process, rather than at its first\n * dispatch. That is the recovery half of a durable driver: the jobs a previous\n * process left behind are claimed by whichever one is up, and a replica that\n * serves no dispatch of its own would otherwise never look.\n *\n * Called by the kernel after every provider has booted, not from `boot()`\n * above: queued listeners are registered as jobs by the event provider, which\n * boots after this one, and a job claimed before its name is registered is\n * dead-lettered for it.\n *\n * Not the memory driver, which never has anything left behind. Not a console\n * command or a migration, which boot the same providers and would claim jobs\n * only to exit under them — `ROOT_DIR` is set by `Server.start()` and the\n * `gemi queue:work` worker alone. Not a server started with\n * `GEMI_QUEUE_CLAIM=off`, which leaves its jobs to workers. Not\n * a `worker` job's thread, which clones the application. And not development,\n * where `bun --hot` boots a fresh application on every save: there the queue\n * starts at the first dispatch, and a reload hands a running loop over — see\n * `takeOverDevQueue`.\n */\nexport function startClaimingIfServing(application: Application) {\n warnAboutClaimSwitch(application);\n if (process.env.NODE_ENV !== \"production\") return takeOverDevQueue(application);\n if (configuresMemory(application)) return;\n if (!claimsInThisProcess()) return;\n if (!application.bound(QueueManager)) return;\n application.make(QueueManager).start();\n}\n\nfunction configuresMemory(application: Application) {\n const driver = application.config.get<QueueConfig>(\"queue\", {}).driver ?? \"memory\";\n return driver === \"memory\" || driver instanceof MemoryQueueDriver;\n}\n\n/**\n * Whether the queue's jobs live only in this process, for the warning below.\n * The config says so for `\"memory\"` or a `MemoryQueueDriver`, but a factory\n * only says once it has run, so for one the manager is built — which is the\n * same driver `durable` and `push()` go by. A factory that throws here is\n * left to throw at the first dispatch, as it would have without the switch,\n * rather than failing the boot over a warning.\n */\nfunction keepsJobsInProcess(application: Application) {\n if (configuresMemory(application)) return true;\n const driver = application.config.get<QueueConfig>(\"queue\", {}).driver;\n if (typeof driver !== \"function\" || !application.bound(QueueManager)) return false;\n try {\n return !application.make(QueueManager).durable;\n } catch {\n return false;\n }\n}\n\n/**\n * Says so at boot when a server's `GEMI_QUEUE_CLAIM` will not do what it\n * reads as. Only in a server's main thread: a console command or a `worker`\n * job's thread shares the server's environment and claims nothing anyway.\n *\n * - `off` with the memory driver is ignored, because a memory queue's jobs\n * exist only in the process that dispatched them. Honoured, it would leave\n * every dispatch waiting in a `Map` that no worker can see, for good.\n * - Any value but `off` or `on` claims. `false` or `0` read as \"off\" and are\n * not it, and a web process that quietly keeps claiming is the failure a\n * deploy that set one would never notice.\n */\nfunction warnAboutClaimSwitch(application: Application) {\n const value = process.env.GEMI_QUEUE_CLAIM;\n if (value === undefined || isQueueWorker()) return;\n if (process.env.ROOT_DIR === undefined || !isMainThread) return;\n if (claimingTurnedOff()) {\n if (!keepsJobsInProcess(application)) return;\n console.warn(\n `[gemi] GEMI_QUEUE_CLAIM=off is ignored: the memory queue driver keeps ` +\n `jobs in the process that dispatched them, so no worker could run ` +\n `them. This server runs its own jobs. Use a driver other processes ` +\n `can read, such as \"database\", to hand jobs to \\`gemi queue:work\\`.`,\n );\n } else if (value.trim().toLowerCase() !== \"on\") {\n console.warn(\n `[gemi] GEMI_QUEUE_CLAIM=\"${value}\" is not \"off\" or \"on\", so this ` +\n `server claims jobs. Set it to \"off\" to leave them to \\`gemi queue:work\\`.`,\n );\n }\n}\n\n/**\n * Under `gemi dev`, stops the loop the previous application started over a\n * shared driver, and starts this application's in its place.\n *\n * A `bun --hot` reload boots a new application in the same process, and the\n * old application's loop — started lazily by a dispatch — is a live closure\n * that nothing stopped. Over the database driver it went on claiming rows\n * from the table and running them with the code from before the save, and\n * after a few saves several generations of it were doing so side by side.\n *\n * Stopped rather than drained: `stop()` claims nothing more and returns, and\n * a job the old loop is running finishes on its old code and reports as\n * usual, where waiting for it would hold up the reload behind a job. Its row\n * stays claimed until then, so the new loop cannot take it twice.\n *\n * The new loop is started only when an old one was running, which keeps the\n * rule that development claims nothing until something is dispatched, while\n * a job waiting out a retry when the file was saved still gets its retry —\n * on the new code, which is the point of the reload. A memory queue is never\n * recorded, so it is never handed over: only its own application feeds it,\n * and stopping it would drop whatever was still waiting there.\n *\n * Runs from every boot. `waitForBoot` twice on one application stops its own\n * loop and starts it again at once, which changes nothing. The `bound` guard\n * is for an application built without the queue provider, which still stops\n * the stale loop.\n */\nfunction takeOverDevQueue(application: Application) {\n const previous = globalThis.__gemiDevQueue;\n if (!previous) return;\n globalThis.__gemiDevQueue = undefined;\n void previous.stop();\n if (application.bound(QueueManager)) application.make(QueueManager).start();\n}\n",
|
|
7
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport type { ChangeFeedConfig } from \"./config\";\nimport { ChangeFeedManager } from \"./ChangeFeedManager\";\n\nexport class ChangeFeedServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n ChangeFeedManager,\n () =>\n new ChangeFeedManager(this.app.config.get<ChangeFeedConfig>(\"changeFeed\", {}), {\n application: this.app,\n }),\n );\n }\n\n /**\n * Ends every subscription and closes the `LISTEN` connection. Streams have\n * already ended by then: each stops at its next keepalive once the server\n * starts shutting down, so the request drain does not wait on them.\n */\n async shutdown() {\n if (!this.app.resolved(ChangeFeedManager)) return;\n await this.app.make(ChangeFeedManager).close();\n }\n}\n",
|
|
8
|
-
"import { ServiceProvider } from \"../support/ServiceProvider\";\nimport type { AuthConfig } from \"./config\";\nimport { AuthManager } from \"./AuthManager\";\n\nexport class AuthServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n AuthManager,\n () => new AuthManager(this.app.config.get<AuthConfig>(\"auth\", {})),\n );\n }\n}\n",
|
|
9
|
-
"import { kernelContext } from \"../../kernel/context\";\nimport { LockManager } from \"../lock/LockManager\";\nimport { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { discoverCronJobs } from \"../discovery\";\nimport { scheduleConfigDefaults, type ScheduleConfig } from \"./config\";\nimport { Scheduler } from \"./Scheduler\";\n\n/**\n * How far inside the provider deadline the drain stops, so there is time left\n * to name the ticks it abandoned before `Application` stops waiting for this\n * provider. The same margin as the queue's drain, for the same reason: a\n * tenth of the budget, capped, so a deadline of a few milliseconds still\n * spends most of itself waiting.\n */\nconst REPORT_MARGIN_MS = 100;\n\nconst reportMargin = (timeoutMs: number) => Math.min(REPORT_MARGIN_MS, Math.floor(timeoutMs / 10));\n\nexport class ScheduleServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n Scheduler,\n () =>\n new Scheduler(\n withDefaults(\n scheduleConfigDefaults(),\n this.app.config.get<ScheduleConfig>(\"schedule\", {}),\n ),\n ),\n );\n }\n\n /**\n * Resolves the schedule, then starts it.\n *\n * Cron jobs are scheduled in phase two, not in `register()`: a job body is\n * free to resolve any service, and by the time `boot()` runs every provider\n * has registered. Each tick re-enters the application context so the job sees\n * the same container a request handler would.\n *\n * ### Why the raw slice and not the scheduler's config\n *\n * The decision here is whether the app said anything, and by the time the\n * scheduler holds a config it can no longer tell: `withDefaults` treats an\n * absent key and an `undefined` one alike and substitutes the default `[]`,\n * which is the same value an app writes when it means \"nothing scheduled, and\n * I mean it\". Reading the slice before defaults are applied is the only place\n * that difference still exists.\n *\n * So: `jobs` present, including `jobs: []`, is used verbatim and no directory\n * is read. Absent or `undefined` — which includes an app with no `schedule`\n * slice at all — the classes under `jobsDir` are, which is the case #323 is\n * about: a cron job that is written and never listed fires never, and nothing\n * downstream is waiting to notice.\n */\n async boot() {\n const scheduler = this.app.make(Scheduler);\n const slice = this.app.config.get<ScheduleConfig>(\"schedule\", {});\n\n if (slice.jobs === undefined) {\n const { jobsDir } = withDefaults(scheduleConfigDefaults(), slice);\n scheduler.useJobs(await discoverCronJobs(jobsDir));\n }\n\n // Resolved, listed, and not started.\n //\n // `gemi run` sets this on the process it spawns. Booting the application is\n // how a console command reaches the container, and starting the schedule is\n // a side effect of that which nobody asked for: a `gemi run backfill` that\n // takes four minutes would otherwise fire the application's whole cron\n // schedule in a process no operator is watching, and `Bun.cron` handles hold\n // the loop open besides.\n //\n // Deliberately above `start` and below `useJobs`, so `app(Scheduler).jobs`\n // still answers honestly — a command that wants to fire a tick by hand can,\n // and a test asking what this application schedules gets the same answer\n // either way.\n //\n // An environment variable rather than a config field, because this is a\n // property of *this process* and not of the application: the same variable\n // is how a deploy runs one cron dyno beside several web ones, and a config\n // slice would have to know which command started it.\n if (process.env.GEMI_NO_SCHEDULE === \"1\") return;\n\n const app = this.app;\n // The queue's locks, so `withoutOverlapping` and `onOneServer` hold across\n // the instances sharing its storage. Resolved per tick that asks.\n if (app.bound(LockManager)) scheduler.useLocks(() => app.make(LockManager));\n scheduler.start((cb) => kernelContext.run(app, cb));\n }\n\n /**\n * Stops the schedule and waits for the ticks already running, on the way out\n * of a server told to stop.\n *\n * Stopping first is the point. Without it `Bun.cron` kept firing for the\n * whole drain window, starting ticks the process was about to exit under,\n * and a tick already running when the process exited was cut off halfway\n * through its `callback` — on a platform that recycles replicas on every\n * release, that is every release.\n *\n * The wait is bounded a little inside what is left of the shared provider\n * deadline, like the queue's, so that this is what names the ticks it gave\n * up on rather than `Application`'s generic \"did not finish within the\n * provider shutdown deadline\", which names no job at all. *\n * Ticks left running resolve `{ abandoned: true }`, so `Application`\n * reports this provider in `timedOut` and the process exits 1, as it does\n * for a queued job cut off at the deadline (#580).\n */\n async shutdown(options?: { timeoutMs: number }) {\n // `boot()` always resolves it; this is for an application that shuts down\n // without having booted, where building a scheduler would be pointless.\n if (!this.app.resolved(Scheduler)) return;\n const scheduler = this.app.make(Scheduler);\n if (scheduler.running > 0) {\n console.log(`[gemi] Shutting down: waiting for ${scheduler.running} running cron tick(s).`);\n }\n const budget = options?.timeoutMs;\n const { unfinished } = await scheduler.drain(\n budget === undefined ? Infinity : Math.max(0, budget - reportMargin(budget)),\n );\n if (unfinished.length > 0) {\n console.error(\n `[gemi] Cron jobs still running at shutdown: ` +\n unfinished\n .map((tick) => `${tick.name} (started ${tick.startedAt.toISOString()})`)\n .join(\", \"),\n );\n return { abandoned: true };\n }\n }\n}\n",
|
|
10
|
-
"import * as registry from \"../../../orm/registry\";\nimport {\n FeatureFlagSource,\n FeatureModelMissingError,\n type FeatureWriteMeta,\n type FeatureWriteResult,\n} from \"./FeatureFlagSource\";\n\n/**\n * Reads the application's `FeatureFlag` table — one row per feature, carrying\n * one meaningful column.\n *\n * The model is resolved from the ORM registry **by name, at call time**, the way\n * `auth/UserProvider` resolves `User`. The framework cannot import a class the\n * application generates, and resolving lazily also means constructing this\n * source does not require the app's models to have been imported yet — only\n * loading does.\n */\nexport class DatabaseFeatureFlagSource extends FeatureFlagSource {\n constructor(readonly modelName: string = \"FeatureFlag\") {\n super();\n }\n\n /** Whether the application has registered the model at all. */\n get available(): boolean {\n return registry.has(this.modelName);\n }\n\n async load(): Promise<Record<string, unknown>[]> {\n if (!this.available) {\n throw new FeatureModelMissingError(this.modelName);\n }\n\n const model = registry.get<any>(this.modelName);\n\n // `asSystem` for the same reason auth uses it: features are read before\n // there is a user — on an anonymous page load, in a cron tick — and a policy\n // that denies by default would turn \"nobody is signed in\" into \"no rows\",\n // which silently switches every feature off for logged-out traffic.\n return await model.asSystem(() => model.findMany());\n }\n\n /**\n * Upserts the row for `key`.\n *\n * `upsert`, not `update`: a feature that has been deployed but never switched\n * on has no row at all, and that is exactly the state an admin screen exists\n * to change.\n *\n * The actor goes into an `updatedBy` column **when the model has one**, and is\n * otherwise not recorded. The column is optional so that adding `Features.set`\n * did not become a migration every app owes. When it exists it is always\n * written — `null` for a write with no actor — because leaving the previous\n * value in place would credit the last named person with a change they did\n * not make.\n */\n async write(key: string, active: boolean, meta: FeatureWriteMeta): Promise<FeatureWriteResult> {\n if (!this.available) {\n throw new FeatureModelMissingError(this.modelName);\n }\n\n const model = registry.get<any>(this.modelName);\n const actorRecorded = this.hasActorColumn(model);\n const data: Record<string, unknown> = { active };\n if (actorRecorded) data.updatedBy = meta.actor;\n\n // `asSystem` for the same reason `load()` uses it: the table is global\n // configuration, and a policy that denied the write would leave the admin\n // with an error about tenancy on a row that has none.\n await model.asSystem(() =>\n model.upsert({\n where: { key },\n create: { key, ...data },\n update: data,\n }),\n );\n\n return { actorRecorded };\n }\n\n private hasActorColumn(model: any): boolean {\n const field = model?.$schema?.fields?.updatedBy;\n return field !== undefined && field.type === \"String\";\n }\n}\n",
|
|
11
|
-
"import type { HttpRequest } from \"../../http/HttpRequest\";\nimport type { FeatureRegistry } from \"./defineFeature\";\nimport { DatabaseFeatureFlagSource } from \"./sources/DatabaseFeatureFlagSource\";\nimport type { FeatureFlagSource } from \"./sources/FeatureFlagSource\";\nimport type { FeatureContext, FeatureEvaluation } from \"./types\";\n\n// Config key: `features`. Derived from `FeaturesServiceProvider`.\nexport interface FeaturesConfig {\n /**\n * The application's declarations — the default export of\n * `app/features/index.ts`.\n *\n * Named here as well as in the framework's `gemi.d.ts` because the two layers\n * cannot share one reference: the type augmentation resolves `@/app/features`\n * through the app's tsconfig paths, which exists only at compile time. This is\n * the runtime half. They should point at the same object.\n */\n features?: FeatureRegistry;\n\n /**\n * Master switch. `false` skips loading and evaluation entirely: every feature\n * reads off and the SSR payload carries `{}`.\n */\n enabled?: boolean;\n\n /** Where the on/off switches come from. The database by default. */\n source?: FeatureFlagSource;\n\n /** ORM registry name of the model, when using the database source. */\n model?: string;\n\n /**\n * Snapshot lifetime in **seconds**.\n *\n * This is the propagation delay: switching a feature on or off is live on\n * every instance within this window. Lower it if that matters more than the\n * query volume — there is no cross-instance invalidation.\n */\n ttl?: number;\n\n /**\n * Extra attributes for every evaluation — country, cohort, build, plan.\n * Reachable in a `when` as `ctx.attributes`.\n *\n * Runs on every request inside the render path, so keep it cheap and free of\n * I/O. If it throws, evaluation degrades to no attributes rather than failing\n * the page.\n */\n context?: (req: HttpRequest | null) => Record<string, unknown> | Promise<Record<string, unknown>>;\n\n /**\n * Called once per key per request after evaluation — the hook an analytics or\n * experiment pipeline reads exposures from. Errors are caught and logged.\n */\n onEvaluate?: (key: string, evaluation: FeatureEvaluation, ctx: FeatureContext) => void;\n\n /** Warn once per boot above this many client-visible features. */\n maxClientFlags?: number;\n}\n\nexport function defineFeaturesConfig(config: FeaturesConfig): FeaturesConfig {\n return config;\n}\n\nexport function featuresConfigDefaults(): Required<\n Omit<FeaturesConfig, \"features\" | \"context\" | \"onEvaluate\">\n> & {\n features: FeaturesConfig[\"features\"];\n context: FeaturesConfig[\"context\"];\n onEvaluate: FeaturesConfig[\"onEvaluate\"];\n} {\n return {\n features: undefined,\n enabled: true,\n source: new DatabaseFeatureFlagSource(),\n model: \"FeatureFlag\",\n ttl: 30,\n context: undefined,\n onEvaluate: undefined,\n maxClientFlags: 200,\n };\n}\n\nexport type ResolvedFeaturesConfig = ReturnType<typeof featuresConfigDefaults>;\n",
|
|
12
|
-
"import { Log } from \"../../facades/Log\";\nimport { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { featuresConfigDefaults, type FeaturesConfig } from \"./config\";\nimport { FeatureManager } from \"./FeatureManager\";\nimport { DatabaseFeatureFlagSource } from \"./sources/DatabaseFeatureFlagSource\";\n\nexport class FeaturesServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(FeatureManager, () => {\n const declared = this.app.config.get<FeaturesConfig>(\"features\", {});\n const config = withDefaults(featuresConfigDefaults(), declared);\n\n // The default source is constructed by `featuresConfigDefaults()` with the\n // default model name, so a `model` override would otherwise be ignored\n // unless the app also replaced the source. Rebuild it here instead of\n // making every app that renames the table also know about the source.\n //\n // Keyed on what the *application* wrote, not on the merged values. The\n // merged `model` is always set — it defaults to `\"FeatureFlag\"` — so\n // comparing it against the source's name would fire for an app that\n // configured only `source: new DatabaseFeatureFlagSource(\"Flags\")`,\n // throwing that source away and pointing the replacement at a table the\n // app never mentioned. Every feature would then read off with nothing to\n // explain why.\n //\n // An explicit `source` always wins: it is the more specific statement, and\n // it is the only one of the two that can name something other than the\n // local table.\n if (declared?.model !== undefined && declared?.source === undefined) {\n config.source = new DatabaseFeatureFlagSource(declared.model);\n } else if (\n declared?.model !== undefined &&\n declared.source instanceof DatabaseFeatureFlagSource &&\n declared.source.modelName !== declared.model\n ) {\n Log.warning(\n `app/config/features.ts sets \\`model: \"${declared.model}\"\\` and a source reading \"${declared.source.modelName}\". The source wins; drop \\`model\\` to silence this.`,\n );\n }\n\n return new FeatureManager(config, (message) => Log.warning(message));\n });\n }\n\n async boot() {\n const features = this.app.make(FeatureManager);\n if (!features.config.enabled) return;\n\n // Warmed at boot so the first request is served from memory rather than\n // paying for the cold load. Wrapped because a database that is not up yet\n // must not fail the boot — the store fails closed, every feature reads off,\n // and it recovers on its own once the next TTL elapses.\n try {\n await features.refresh();\n } catch (error) {\n Log.error(\n `Could not warm the feature flag cache at boot. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n}\n",
|
|
13
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport type { MailConfig } from \"./config\";\nimport { MailManager } from \"./MailManager\";\n\nexport class MailServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n MailManager,\n () => new MailManager(this.app.config.get<MailConfig>(\"mail\", {})),\n );\n }\n}\n",
|
|
14
|
-
"import { app } from \"../../foundation/app\";\nimport { Job } from \"../queue/Job\";\nimport { EventManager } from \"./EventManager\";\nimport type { Listener, ListenerClass } from \"./Listener\";\n\n/**\n * The adapter that lets `queued = true` be one line: a `Job` subclass, built at\n * registration, that runs exactly one listener.\n *\n * Retries, `maxAttempts`, dead-lettering, `concurrency` and worker-thread\n * execution are four features the queue already has, already documents for\n * applications, and is already tested on. A second queue for listeners would be\n * four features that are almost the same as those, differing in ways nobody\n * decided. So a queued listener is not queued by anything written here — it is\n * a job, and everything after `push` is the queue's.\n */\n\n/**\n * The name a listener's synthetic job is registered under.\n *\n * Prefixed, and visibly so. The synthetic jobs land in\n * `QueueManager.registeredJobs` alongside the app's own, because that getter is\n * documented as reporting what the manager was handed and a queued listener\n * genuinely is something the queue will run — hiding them would make a queue\n * introspection tool lie about what is about to execute. The prefix is what\n * keeps an author from reading `SendWelcomeEmail` there and looking for a job\n * file they never wrote.\n */\nexport function listenerJobName(listenerName: string): string {\n return `listener:${listenerName}`;\n}\n\n/**\n * Builds the `Job` subclass that runs one queued listener.\n *\n * `instance` is the listener the caller already constructed to read `queued`\n * off; the fields that decide how and where the work runs are read from that one\n * instance rather than from a fresh one per attempt, so a listener's\n * constructor runs once at boot instead of once per retry.\n *\n * What crosses the queue is `[eventName, constructorArguments]` as JSON — a\n * name and the arguments, never the event instance and never the classes. That\n * is invariant 1 at its second boundary: the class objects on the pushing side\n * and the running side can come from two different module graphs (a minified\n * bundle and a source-side discovery walk), so a name declared as a string\n * literal is the only thing both ends agree on. `EventManager.rehydrate` turns\n * it back into an event.\n */\nexport function jobForListener(\n listener: ListenerClass,\n instance: Listener,\n): new () => Job {\n refuseImplicitName(listener);\n\n const attempts = instance.maxAttempts;\n const delays = instance.backoff;\n const inWorker = instance.worker;\n const boundTo = listener.event.name;\n\n const job = class extends Job {\n maxAttempts = attempts;\n backoff = delays;\n worker = inWorker;\n\n async run(...payload: unknown[]) {\n const [eventName, args] = readPayload(payload);\n const event = app(EventManager).rehydrate(eventName, args);\n await new listener().handle(event);\n }\n\n // The queue's failure path is silent unless a job writes to it: `onFail`\n // and `onDeadletter` on `Job` are empty bodies, and `QueueManager.run`\n // routes every rejection into exactly those two. Without these overrides a\n // queued listener that throws produces no output anywhere — `dispatch`\n // returned long ago, the sync path's \"the listener X threw while handling\n // Y\" line is on a branch this listener never takes, and the last thing that\n // happens is the entry leaving the queue. A welcome email that stopped\n // being sent in production would look exactly like one that was never\n // dispatched.\n //\n // That is also what makes `rehydrate`'s throw the informative failure it is\n // documented as: a payload naming an event this process does not know is\n // the shape a `worker = true` listener hits when its thread booted a\n // different build of the application, and it is unreachable from the\n // dispatcher's stack.\n //\n // Both hooks, not one. `onFail` carries the error and fires per attempt, so\n // a listener failing differently on each is visible; `onDeadletter` fires\n // once and reports the fact no attempt is left, which is a different thing\n // to know and the only line that means the work is gone. The final attempt\n // logs both, deliberately.\n //\n // The event is named from `static event` rather than from the payload:\n // reading the payload here means parsing it, and a throw out of either hook\n // would escape through `QueueManager.run`'s `catch` into a `run()` nobody\n // awaits.\n onFail(error: Error) {\n console.error(\n `The listener ${listener.name} threw while handling ${boundTo} off ` +\n `the queue. The queue will retry it up to ${attempts} times in ` +\n `total; nothing the dispatcher did is waiting on it.`,\n error,\n );\n }\n\n onDeadletter(error: Error) {\n console.error(\n `The listener ${listener.name} failed every one of its ${attempts} ` +\n `attempts at ${boundTo} and has been dead-lettered, so this side ` +\n `effect did not happen and will not be retried.`,\n error,\n );\n }\n };\n\n // The `value` is load-bearing: the queue keys its registry off `job.name`,\n // so without it the entry would be named after whatever binding this class\n // expression picked up. `writable: true` is the other half, and nothing\n // reads it today — it is here so the descriptor matches the one a declared\n // `static name = \"...\"` field produces. Every \"was this name declared?\"\n // check in the framework is a descriptor read rather than a value read\n // (`discovery.ts`, and `refuseImplicitName` below), because in development\n // the declared and the implicit spelling read the same string; and\n // redefining `name` without the flag leaves the class's own non-writable\n // descriptor in place. So a check later pointed at a *registered* job rather\n // than a discovered one — the queue's registry is the obvious place to move\n // one — would report every synthetic entry as a job the author never wrote,\n // under a fix they cannot apply. Cheap to keep, wrong to drop.\n Object.defineProperty(job, \"name\", {\n value: listenerJobName(listener.name),\n writable: true,\n });\n\n return job;\n}\n\n/**\n * Refuses to queue a listener whose name is the implicit class binding.\n *\n * A sync listener's name is only ever read inside one module graph, so\n * discovery warns about this and moves on. A queued one puts the name on the\n * wire: the push side looks the job up by `listener:<name>` in its registry and\n * the running side looks it up in *its* registry, and those two can be\n * different builds of the same application — the main process discovering\n * `app/listeners` from source while a `worker = true` listener runs in a thread\n * that imported `dist/server/bootstrap.mjs`, where minification renamed the\n * class. The names then disagree, `dispatchJob` resolves nothing, the worker\n * reports success, and the side effect silently stops happening in production.\n *\n * A throw rather than a warning because the failure is invisible from every\n * side once it happens, and because the fix is one line the author can read in\n * the message.\n */\nfunction refuseImplicitName(listener: ListenerClass): void {\n if (Object.getOwnPropertyDescriptor(listener, \"name\")?.writable) return;\n\n throw new Error(\n `The listener ${listener.name} sets \\`queued = true\\` but does not ` +\n `declare \\`static name\\`, so it would be registered with the queue ` +\n `under its class name — which a production build renames while the ` +\n `payload that names it does not. Add: static name = \"${listener.name}\";`,\n );\n}\n\n/**\n * The payload, whichever of the two shapes the queue handed it over in.\n *\n * The queue calls `run` two different ways and this is the only place that has\n * to know. In-process, `QueueManager.run` spreads the parsed arguments:\n * `run(\"UserRegistered\", [7, \"ada@example.com\"])`. Through a worker thread the\n * far side is `QueueManager.dispatchJob`, which passes the parsed array whole:\n * `run([\"UserRegistered\", [7, \"ada@example.com\"]])`. Reading only the first\n * shape would make `worker = true` a listener that receives an event named\n * `undefined` — a throw out of `rehydrate`, in a worker thread, retried to\n * dead-letter.\n *\n * The two are told apart by what is in the first slot rather than by counting\n * arguments, because the payload's own first element is always the event name\n * and a name is always a string.\n */\nfunction readPayload(payload: unknown[]): [string, unknown[]] {\n return (typeof payload[0] === \"string\" ? payload : payload[0]) as [\n string,\n unknown[],\n ];\n}\n",
|
|
15
|
-
"import { app } from \"../../foundation/app\";\nimport { deferUntilCommit } from \"../../orm/context\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport type { Job } from \"../queue/Job\";\nimport { QueueManager } from \"../queue/QueueManager\";\nimport { eventConfigDefaults, type EventConfig } from \"./config\";\nimport type { Event, EventClass } from \"./Event\";\nimport { jobForListener } from \"./listenerJob\";\nimport type { ListenerClass } from \"./Listener\";\n\n/**\n * One listener, and where it runs.\n *\n * `queued` is an instance field, so answering \"does this one go on the queue?\"\n * means constructing the listener — done once here, at registration, rather\n * than once per dispatch. The synthetic job is built at the same moment for the\n * same reason: `EventServiceProvider.boot()` has to hand it to the queue before\n * the first dispatch, and a job built per dispatch would be a different class\n * object each time under the same registry key.\n */\ntype Registration = {\n listener: ListenerClass;\n /** The job a queued listener is pushed as, or `null` when it runs inline. */\n job: (new () => Job) | null;\n};\n\n/**\n * The registry a dispatch fans out through: event name -> the listeners bound\n * to it.\n *\n * Shaped after `QueueManager` deliberately — constructed in `register()` from\n * whatever the config slice declared, handed the discovered set in `boot()`,\n * and reporting through a `registered*` getter what it was *given* rather than\n * what it accepted, so a test can see a collision instead of having it tidied\n * away.\n *\n * Three things are not `QueueManager`'s, and each is a decision rather than a\n * detail:\n *\n * **Many listeners per key.** Two listeners for one event is the normal case\n * here, where two jobs for one name is the pathological one. So the map holds\n * arrays and `useListeners` appends. The collision it does refuse is two\n * *listener classes* claiming one `static name`.\n *\n * **Registration order is the walk's order, and means nothing.** It is stable —\n * `discoverClasses` sorts per directory and the module namespace sorts by name\n * — so it is reproducible, but no listener may depend on running before\n * another, because nothing about a filesystem layout was chosen to express\n * that. What makes it safe to say is the rule below: nothing a listener does\n * can stop the next one.\n *\n * **A dispatch nobody handles warns.** That is the entire early-warning system\n * for the subsystem, and it is why it is not optional; see `dispatchAndWait`.\n *\n * A `queued` listener is not run here at all. It is registered with the\n * `QueueManager` as a synthetic job and pushed to it on dispatch, so retries,\n * `maxAttempts`, dead-lettering and worker threads are the queue's rather than\n * a second implementation of the queue's — and nothing the request had (the\n * current actor, the ambient transaction) is still promised by the time it\n * runs. See `Listener.queued`.\n *\n * **A dispatch does not always fan out at the dispatch.** An event declaring\n * `static afterCommit` has the whole of it — queued listeners included — held\n * on the ORM's transaction scope and drained when that transaction commits, or\n * dropped when it rolls back. That is the only thing in this file that reads\n * ambient state, and `deferToCommit` is where it happens.\n */\nexport class EventManager {\n static token = \"events\";\n\n /** Event name -> every listener bound to it, in registration order. */\n private listeners: Record<string, Registration[]> = {};\n\n /**\n * Event name -> the class to rebuild it from, for a listener coming back off\n * the queue.\n *\n * Read from each listener's `static event` at registration, which is the one\n * place in the subsystem an event class is dereferenced. An event nobody\n * listens for is never in here, and that is exactly right: nothing can have\n * been queued for it.\n */\n private events: Record<string, EventClass> = {};\n\n /**\n * Event names already warned about. Per-manager, and the manager is per\n * application, so a test that builds a fresh kernel gets a fresh set.\n */\n private warnedFor = new Set<string>();\n\n /**\n * Event names already warned about for the `dispatchAndWait` + `afterCommit`\n * pairing. A second set rather than a second entry in the one above: the two\n * warnings are about different mistakes, and sharing the set would mean a\n * dispatch that legitimately has no listeners silences the one that says a\n * caller is awaiting nothing.\n */\n private warnedAboutWaiting = new Set<string>();\n\n readonly config: Required<EventConfig>;\n\n constructor(config: EventConfig = {}) {\n this.config = withDefaults(eventConfigDefaults(), config);\n this.useListeners(this.config.listeners);\n }\n\n /**\n * Replaces the registered set, for the provider to hand over what it found\n * under `app/listeners`.\n *\n * Two phases, for the reason `QueueManager.useJobs` documents: the manager is\n * constructed in `register()`, which is synchronous and must resolve nothing,\n * while reading a directory and importing what is in it is neither. So the\n * manager is built from whatever the config slice declared — nothing, when\n * the app left `listeners` out — and the discovered set arrives in `boot()`.\n *\n * The consequence worth knowing: anything that constructs an application and\n * skips phase two dispatches into an empty registry, and an empty registry is\n * a legal steady state rather than an error. The development-only warning\n * below is the only thing that says so.\n *\n * Every listener is **constructed once here**, to read the fields that decide\n * where it runs — `queued`, and through it `maxAttempts` and `worker`. A\n * listener's constructor therefore runs at boot as well as on every dispatch,\n * so it wants to assign fields and nothing else. A queued one also has its\n * synthetic job built here; `EventServiceProvider` hands those to the queue\n * once boot reaches it, so calling this again *after* boot would leave the\n * queue holding jobs for listeners this registry no longer has.\n */\n useListeners(listeners: ListenerClass[]) {\n this.config.listeners = listeners;\n\n this.listeners = {};\n this.events = {};\n const claimed = new Set<string>();\n\n for (const listener of listeners) {\n // Declared as required on the base class and still checked here: every\n // subclass inherits the declaration whether or not it assigns to it, so\n // `typeof SomeListener` satisfies `ListenerClass` either way and the\n // compiler cannot see the omission. Refused rather than registered under\n // some fallback, because there is no fallback — a listener with no event\n // is a file the author wrote that nothing will ever call.\n if (!listener.event) {\n console.error(\n `Listener ${listener.name} does not declare \\`static event\\`, so ` +\n `there is no event name to register it under and its handle will ` +\n `never run. Add: static event = SomeEvent;`,\n );\n continue;\n }\n\n // Two classes with one name, refused here and nowhere else — which is\n // what keeps the queue from being handed two jobs called\n // `listener:NotifyAdmins` and having to decide between them one layer\n // down, where the loser is a side effect that reports success. A\n // directory walk is what makes the clash ordinary, since nothing forces\n // the import alias a hand-written list would have demanded.\n if (claimed.has(listener.name)) {\n console.error(\n `Two event listeners are named \"${listener.name}\" — the first is ` +\n `registered and this one is not. A listener's name identifies it ` +\n `across the framework, so only one class can hold it. Rename one.`,\n );\n continue;\n }\n claimed.add(listener.name);\n\n // The one place the event class is dereferenced, and deliberately the\n // only one: this runs in the module graph that declared the class, so the\n // name it yields is the source-side one that discovery and a minified\n // dispatcher both agree on. Nothing downstream keeps the class object —\n // except `this.events`, which keeps it under that same string so a\n // listener coming back off the queue has something to rebuild from. First\n // claim wins there too: a name is what identifies an event to everything\n // downstream, so two classes declaring one are one event already.\n const name = listener.event.name;\n const instance = new listener();\n\n this.events[name] ??= listener.event;\n (this.listeners[name] ??= []).push({\n listener,\n job: instance.queued ? jobForListener(listener, instance) : null,\n });\n }\n }\n\n /**\n * The synthetic jobs the queue has to be told about — one per queued\n * listener, named `listener:<name>`.\n *\n * `EventServiceProvider.boot()` is the only caller. It reads this rather than\n * having the manager reach for the `QueueManager` itself, because\n * registration happens in `register()`, where resolving another service is\n * exactly what a provider must not do. A copy, so the caller cannot edit what\n * a dispatch pushes.\n */\n get queuedListenerJobs(): ReadonlyArray<new () => Job> {\n return Object.values(this.listeners)\n .flat()\n .flatMap(({ job }) => (job ? [job] : []));\n }\n\n /**\n * What the manager was handed, discovered or declared.\n *\n * \"Is this listener registered?\" is a question with a silent wrong answer —\n * an unregistered listener is a side effect that does not happen, and nothing\n * is waiting on it to notice. This is where a test asks it out loud.\n *\n * It reports what came in, not what the registry accepted, so a name claimed\n * twice appears twice here — deliberately, the same way `QueueManager` and\n * `Scheduler` do. A copy, so a walk over it cannot edit what dispatch reads.\n */\n get registeredListeners(): ReadonlyArray<ListenerClass> {\n return [...this.config.listeners];\n }\n\n /**\n * Fires the event and returns. The sync listeners run to completion after the\n * caller has moved on.\n *\n * Nothing is awaited and nothing can reject: `runListeners` catches every\n * listener's failure itself, so the floating promise here has no rejection to\n * float.\n *\n * `args` is the event's constructor arguments; see `dispatchAndWait`.\n *\n * On an `afterCommit` event inside a transaction, \"after the caller has moved\n * on\" becomes \"after that transaction commits\" — see `deferToCommit`.\n */\n dispatch(event: Event, args?: readonly unknown[]): void {\n if (this.deferToCommit(event, args, false)) return;\n void this.runListeners(event, args);\n }\n\n /**\n * Fires the event and resolves once every **sync** listener has settled.\n *\n * On an `afterCommit` event inside an open transaction it resolves\n * **immediately, having run nothing** — there is no listener to wait for yet,\n * and the transaction it would be waiting on is the caller's own. That is the\n * sharp edge `Event.afterCommit` documents and the reason the flag ships\n * opt-in; it warns once per event name in development, because nothing else\n * about the call site can show it.\n */\n async dispatchAndWait(\n event: Event,\n args?: readonly unknown[],\n ): Promise<void> {\n if (this.deferToCommit(event, args, true)) return;\n return this.runListeners(event, args);\n }\n\n /**\n * Queues the fan-out on the open transaction when the event asked for that,\n * and reports whether it did — `false` means the caller runs it now.\n *\n * Three cases, and only the third defers: an event without\n * `static afterCommit` behaves exactly as it did before this existed; one\n * with it, dispatched outside a transaction, has nothing to wait for; one\n * with it, inside a transaction, runs when that transaction commits and not\n * at all if it rolls back.\n *\n * `currentTransaction()` is not consulted directly, and the difference\n * matters: `deferUntilCommit` also answers `false` for a scope carrying a\n * handle that `withTransaction` did not open, which has no commit hook and\n * therefore no list that will ever be drained. Reading the handle alone would\n * queue the dispatch onto nothing and lose it silently.\n *\n * The whole fan-out is deferred, sync listeners and queued ones alike. A\n * queued listener is *pushed* at commit rather than at dispatch, which is the\n * only correct reading of `afterCommit`: the queue drains in-process and\n * often synchronously from `push`, so pushing early is running early.\n */\n private deferToCommit(\n event: Event,\n args: readonly unknown[] | undefined,\n awaited: boolean,\n ): boolean {\n // Read off the constructor, the same way the registry key is, and typed\n // structurally for the same reason: `Event` arrives here as a type-only\n // import, and the class object an application dispatches may have come from\n // a different module graph than the one this file was compiled against.\n const { name, afterCommit } = event.constructor as {\n name: string;\n afterCommit?: boolean;\n };\n if (!afterCommit) return false;\n\n const deferred = deferUntilCommit(() => this.runListeners(event, args));\n if (deferred && awaited) this.warnNothingIsWaitedFor(name);\n return deferred;\n }\n\n /**\n * Runs every **sync** listener bound to this event, in registration order,\n * and resolves when the last of them has settled. Every queued one is handed\n * to the `QueueManager` on the way past and is not waited for.\n *\n * The dispatch itself is `dispatch` and `dispatchAndWait` above; this is what\n * they run, and what an `afterCommit` event's transaction runs later. Split\n * out so that \"when does the fan-out happen\" is decided in exactly one place\n * rather than in each entry point.\n *\n * ### Sync and queued in one pass\n *\n * A queued listener is pushed at the point in the order it occupies, so its\n * *dispatch* is ordered with the rest and its *execution* is not: the queue\n * decides when. What that costs is worth knowing — `dispatchAndWait`\n * resolving says every sync listener has finished and says nothing at all\n * about a queued one, which may not have started or may already have failed\n * twice. Whether a listener is queued is therefore a decision about what the\n * caller can rely on having happened, not only about latency.\n *\n * @param args the arguments the event's constructor was called with, which\n * `Event.dispatch` forwards. Only a queued listener needs them: the event\n * instance itself never crosses the queue, and what is rebuilt on the other\n * side is `new EventClass(...args)`. Dispatching a hand-built instance\n * straight at the manager therefore cannot queue, and says so on stderr\n * rather than pushing a payload that would rehydrate into an event with\n * `undefined` fields.\n *\n * ### One at a time, deliberately\n *\n * Each listener is awaited before the next one starts, rather than started\n * together under a `Promise.allSettled`. Concurrency would make \"registration\n * order\" a claim about start order only, and the order it is a claim about is\n * the one `registeredListeners` reports and the discovery tests assert on.\n *\n * What it costs is worth knowing, because it is the one way listener\n * independence is not total: a listener that *hangs* — an un-timed-out\n * `fetch` to a host that never answers — delays every listener after it for\n * as long as it hangs, and under `dispatch` the caller has already moved on,\n * so an audit row a later listener writes simply is not there yet. A throw is\n * not this: that is caught below and the next listener runs immediately. A\n * listener that can block indefinitely is a listener that wants its own\n * timeout, or a `Job`.\n *\n * ### Errors\n *\n * Each listener runs inside its own `try`. A throw is logged with the event\n * name, the listener name, and the error, and the loop continues — listeners\n * are independent side effects, and letting listener 2 cancel 3 through 5\n * would make a filesystem walk's order load-bearing. Neither this nor\n * `dispatch` ever rejects, for the same reason from the caller's side: a\n * rejection would make listener 1's failure the dispatcher's problem while\n * listener 5's silently is not.\n *\n * ### Why zero listeners is worth a line on stderr\n *\n * A registry keyed by the wrong string, a `static name` that does not survive\n * a production build, a typo, and a listener directory that was never walked\n * all produce the same single symptom: nobody handled it. Zero listeners is\n * also a perfectly legal steady state, so the warning is development-only and\n * fires once per event name — a dispatch in a loop would otherwise bury the\n * terminal.\n */\n protected async runListeners(\n event: Event,\n args?: readonly unknown[],\n ): Promise<void> {\n const name = (event.constructor as { name: string }).name;\n const handlers = this.listeners[name];\n\n if (!handlers?.length) {\n this.warnNothingIsListening(name);\n return;\n }\n\n for (const { listener: Listener, job } of handlers) {\n if (job) {\n this.enqueue(job, Listener, name, args);\n continue;\n }\n\n try {\n await new Listener().handle(event);\n } catch (error) {\n console.error(\n `The listener ${Listener.name} threw while handling ${name}. The ` +\n `listeners after it still ran — a listener's failure is its own.`,\n error,\n );\n }\n }\n }\n\n /**\n * Rebuilds an event from what crossed the queue.\n *\n * The queue carries a name and an argument array, because that is all JSON\n * can carry: **the constructor arguments are what is serialized, not the\n * instance** — the same bargain `Job.dispatch` makes. So an event whose\n * constructor does work beyond assigning fields does that work a second time\n * here, in the worker, with none of the dispatching request around it.\n *\n * A name that resolves to nothing throws, and is the one place in this file\n * that does. The payload is already off the queue by the time this runs and\n * the listener is one line away from being handed `undefined`, so the\n * alternative is a `handle` reading `event.email` off nothing, several frames\n * further on, with the name that would have explained it no longer in scope.\n * A throw here is caught by `QueueManager.run` and takes the queue's ordinary\n * retry-then-dead-letter path.\n */\n rehydrate(eventName: string, args: readonly unknown[] = []): Event {\n const EventClass = this.events[eventName];\n\n if (!EventClass) {\n throw new Error(\n `Cannot rebuild the event \"${eventName}\" that came off the queue: no ` +\n `registered listener binds to an event of that name, so there is no ` +\n `class to construct. A queued listener runs in an application that ` +\n `discovered its own listeners — check that this process sees the ` +\n `same app/listeners as the one that dispatched, and that the event ` +\n `declares \\`static name = \"${eventName}\"\\`.`,\n );\n }\n\n return new EventClass(...args);\n }\n\n /**\n * Hands one queued listener to the queue, as `[eventName, args]`.\n *\n * Nothing is awaited: `push` is not waited on even to record the entry,\n * which is the whole of what `queued = true` buys. The event instance the\n * sync listeners share does not go with it — only the name and the arguments\n * do, so nothing that was resolved from the request's context can be smuggled\n * across into an application that does not have it.\n *\n * Failing to queue is caught for the same reason a listener's throw is: it is\n * one listener's problem, and the listeners after it in the walk did not do\n * anything to deserve it. Two things reach that catch — an argument JSON\n * cannot serialise, and an application with no queue bound — and both would\n * otherwise reject `dispatchAndWait`, which is documented never to.\n */\n private enqueue(\n job: new () => Job,\n listener: ListenerClass,\n eventName: string,\n args?: readonly unknown[],\n ) {\n if (args === undefined) {\n console.error(\n `The listener ${listener.name} is queued, but ${eventName} reached ` +\n `the dispatcher without its constructor arguments, so there is ` +\n `nothing for the queue to rebuild it from and the listener was ` +\n `skipped. Dispatch through ${eventName}.dispatch(...) or ` +\n `${eventName}.dispatchAndWait(...), which carry them.`,\n );\n return;\n }\n\n const failed = (error: unknown) =>\n console.error(\n `The listener ${listener.name} could not be queued for ${eventName}, ` +\n `so it did not run and will not be retried. The listeners after it ` +\n `still ran.`,\n error,\n );\n\n // Both halves: the two failures above throw here, and a driver that\n // cannot record the job rejects later — unawaited, so without the `catch`\n // that rejection would be an unhandled one.\n try {\n // `reportFailure: false`: the queue's own line would say only which job\n // could not be recorded, and `failed` names the event, the listener and\n // what it means for the listeners after it.\n app(QueueManager)\n .push(job, JSON.stringify([eventName, args]), { reportFailure: false })\n .catch(failed);\n } catch (error) {\n failed(error);\n }\n }\n\n /**\n * The one warning for the pairing that reads as doing something and does not.\n *\n * `await UserRegistered.dispatchAndWait(...)` inside a transaction, on an\n * event that defers to commit, resolves with nothing having run — the\n * listeners are queued on a transaction the caller has not finished. There is\n * no other symptom: the promise resolves, the listeners do run later, and the\n * only thing that is wrong is that the caller's `await` bought it nothing.\n * Code that reads what a listener wrote on the next line finds it missing,\n * and nothing connects the two.\n *\n * Development only and once per event name, on the same terms as the\n * zero-listener warning: both are legal steady states that are usually a\n * mistake, and a dispatch in a loop must not bury the terminal.\n */\n private warnNothingIsWaitedFor(name: string) {\n if (process.env.NODE_ENV === \"production\") return;\n if (this.warnedAboutWaiting.has(name)) return;\n this.warnedAboutWaiting.add(name);\n\n console.warn(\n `[gemi] ${name}.dispatchAndWait() was called inside a transaction and ` +\n `${name} declares \\`static afterCommit = true\\`, so it resolved ` +\n `immediately with no listener having run — they run when the ` +\n `transaction commits. Await the transaction instead, or drop ` +\n `afterCommit if the caller needs the side effects first. Development ` +\n `only.`,\n );\n }\n\n private warnNothingIsListening(name: string) {\n if (process.env.NODE_ENV === \"production\") return;\n if (this.warnedFor.has(name)) return;\n this.warnedFor.add(name);\n\n console.warn(\n `[gemi] ${name} was dispatched and nothing is listening for it. A ` +\n `listener registers under the name its event declares, so check that ` +\n `app/listeners holds a Listener with \\`static event = ${name}\\` and ` +\n `that ${name} declares \\`static name = \"${name}\"\\`. Development only.`,\n );\n }\n}\n",
|
|
16
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { discoverListeners } from \"../discovery\";\nimport { QueueManager } from \"../queue/QueueManager\";\nimport { eventConfigDefaults, type EventConfig } from \"./config\";\nimport { EventManager } from \"./EventManager\";\n\nexport class EventServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n EventManager,\n () => new EventManager(this.app.config.get<EventConfig>(\"events\", {})),\n );\n }\n\n /**\n * Fills in the listener registry when the app did not declare one.\n *\n * ### Why the raw slice and not the manager's config\n *\n * The decision here is whether the app said anything, and by the time the\n * manager holds a config it can no longer tell: `withDefaults` treats an\n * absent key and an `undefined` one alike and substitutes the default `[]`,\n * which is the same value an app writes when it means \"no listeners, and I\n * mean it\". Reading the slice before defaults are applied is the only place\n * the difference still exists.\n *\n * So: `listeners` present, including `listeners: []`, is used verbatim and no\n * directory is read. Absent or `undefined`, the classes under `listenersDir`\n * are.\n *\n * ### Why phase two\n *\n * Discovery imports every file it walks, which is asynchronous, and\n * `register()` is not. It also has to finish before the first dispatch rather\n * than before the first request — a dispatch against an empty registry is not\n * an error, it is a side effect that quietly does not happen — so the\n * registry has to be complete by the end of boot.\n *\n * ### Why the queue is handed the listener jobs from here\n *\n * A queued listener is registered with the `QueueManager` as a synthetic job,\n * and that registration cannot happen in `register()`: resolving another\n * service is the one thing a `register()` must not do, and the jobs are not\n * known until discovery has run anyway. It also cannot happen before\n * `QueueServiceProvider.boot()` fills the queue's registry, which is why this\n * provider sits after it in `frameworkProviders` — `useJobs` replaces that\n * registry wholesale, so the queue has to be finished with it before\n * `registerJob` starts adding to it.\n *\n * The declared-listeners path falls through to it rather than returning\n * early: an app that lists its listeners in `app/config/events.ts` still has\n * queued ones, and skipping the queue for them would make `queued = true` a\n * field that silently does nothing depending on how the app registers.\n */\n async boot() {\n const manager = this.app.make(EventManager);\n const slice = this.app.config.get<EventConfig>(\"events\", {});\n\n if (slice.listeners === undefined) {\n const { listenersDir } = withDefaults(eventConfigDefaults(), slice);\n manager.useListeners(await discoverListeners(listenersDir));\n }\n\n // Nothing queued, nothing to resolve. An application assembled out of this\n // provider alone — a test, a script — has no `QueueManager` bound, and\n // asking for one it has no use for would turn that into a boot failure.\n const jobs = manager.queuedListenerJobs;\n if (jobs.length === 0) return;\n\n const queue = this.app.make(QueueManager);\n for (const job of jobs) {\n queue.registerJob(job);\n }\n }\n}\n",
|
|
17
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport type { FilesystemConfig } from \"./config\";\nimport { FilesystemManager } from \"./FilesystemManager\";\n\nexport class FilesystemServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n FilesystemManager,\n () =>\n new FilesystemManager(\n this.app.config.get<FilesystemConfig>(\"filesystem\", {}),\n ),\n );\n }\n}\n",
|
|
18
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport type { ImageConfig } from \"./config\";\nimport { ImageManager } from \"./ImageManager\";\n\nexport class ImageServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n ImageManager,\n () => new ImageManager(this.app.config.get<ImageConfig>(\"image\", {})),\n );\n }\n}\n",
|
|
19
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { KernelId } from \"./KernelId\";\n\n// Config-less by nature: the id is generated per process, never declared.\nexport class KernelIdServiceProvider extends ServiceProvider {\n register() {\n this.app.instance(KernelId, new KernelId());\n }\n}\n",
|
|
20
|
-
"import type { LogEntry } from \"./types\";\n\n// Config key: `log`. Derived from `LoggingServiceProvider`.\nexport interface LogConfig {\n maxFileSize?: number;\n\n onLogFileClosed?: (file: File) => void | Promise<void>;\n onLogCreated?: (logEntry: LogEntry) => void | Promise<void>;\n}\n\nexport function defineLogConfig(config: LogConfig): LogConfig {\n return config;\n}\n\nexport function logConfigDefaults(): Required<LogConfig> {\n return {\n maxFileSize: 1024 * 1024 * 10, // 10MB\n onLogFileClosed: () => {},\n onLogCreated: () => {},\n };\n}\n",
|
|
21
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { LogManager } from \"./LogManager\";\nimport { logConfigDefaults, type LogConfig } from \"./config\";\n\nexport class LogServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n LogManager,\n () =>\n new LogManager(\n withDefaults(\n logConfigDefaults(),\n this.app.config.get<LogConfig>(\"log\", {}),\n ),\n ),\n );\n }\n\n /**\n * The log directory is created once at kernel boot rather than on the first\n * `Log.info()`, which is what the old eagerly-constructed container did.\n * Resolving here is deliberate: it is the one service whose readiness is a\n * boot-time side effect, and paying for it lazily mid-request would add file\n * IO to whichever handler happened to log first.\n *\n * Only for a process that is serving, though. `runConsole` boots these same\n * providers for every command, as does a migration or a test with a Kernel,\n * and preparing a log file is not free: a `mkdir`, a `readdir`, and — when\n * the newest file still has room — reading its whole text back into a fresh\n * writer, up to `maxFileSize` (10MB by default), in whatever directory the\n * process was started from. `ROOT_DIR` is what `Server.start` sets and\n * nothing else does. Those contexts stay lazy rather than silent: a command\n * that actually logs still gets its file, because `LogManager.log()` boots\n * the manager on first use.\n */\n async boot() {\n if (!process.env.ROOT_DIR) {\n return;\n }\n await this.app.make(LogManager).boot();\n }\n}\n",
|
|
22
|
-
"import {\n middlewareConfigDefaults,\n type MiddlewareConfig,\n} from \"../../http/middleware-config\";\nimport { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { MiddlewareRegistry } from \"./MiddlewareRegistry\";\n\nexport class MiddlewareServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n MiddlewareRegistry,\n () =>\n new MiddlewareRegistry(\n withDefaults(\n middlewareConfigDefaults(),\n this.app.config.get<MiddlewareConfig>(\"middleware\", {}),\n ),\n ),\n );\n }\n\n boot() {\n // At boot rather than on the first request, so a bad `global` entry stops\n // `gemi start` instead of surfacing as a 500 on every request.\n this.app.make(MiddlewareRegistry).assertGlobalMiddleware();\n }\n}\n",
|
|
23
|
-
"import { InMemoryRateLimiter } from \"./drivers/InMemoryRateLimiterDriver\";\nimport type { RateLimiterDriver } from \"./drivers/RateLimiterDriver\";\n\n// Config key: `ratelimiter`. Derived from `RateLimiterServiceProvider`.\nexport interface RateLimiterConfig {\n /**\n * Where counters live. In-process by default; swap in `RedisRateLimiter` once\n * the app runs on more than one instance.\n */\n driver?: RateLimiterDriver;\n\n /** Requests allowed per window when the `rate-limit` DSL omits a limit. */\n limit?: number;\n\n /** Window length in **seconds** when the `rate-limit` DSL omits one. */\n window?: number;\n}\n\nexport function defineRateLimiterConfig(\n config: RateLimiterConfig,\n): RateLimiterConfig {\n return config;\n}\n\nexport function rateLimiterConfigDefaults(): Required<RateLimiterConfig> {\n return {\n driver: new InMemoryRateLimiter(),\n limit: 1000,\n window: 60,\n };\n}\n",
|
|
24
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { RateLimiter } from \"./RateLimiter\";\nimport { rateLimiterConfigDefaults, type RateLimiterConfig } from \"./config\";\n\nexport class RateLimiterServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n RateLimiter,\n () =>\n new RateLimiter(\n withDefaults(\n rateLimiterConfigDefaults(),\n this.app.config.get<RateLimiterConfig>(\"ratelimiter\", {}),\n ),\n ),\n );\n }\n}\n",
|
|
25
|
-
"import type { RedisOptions } from \"bun\";\n\n// Config key: `redis`. Derived from `RedisServiceProvider`.\nexport interface RedisConfig {\n // Connection URL for Bun's Redis client. Defaults to the `REDIS_URL`\n // environment variable (Bun itself falls back to `redis://localhost:6379`\n // when unset).\n url?: string;\n\n // Optional Bun Redis client options (TLS, timeouts, auto-reconnect, ...).\n options?: RedisOptions;\n}\n\nexport function defineRedisConfig(config: RedisConfig): RedisConfig {\n return config;\n}\n\nexport function redisConfigDefaults(): RedisConfig {\n return {\n url: process.env.REDIS_URL,\n options: undefined,\n };\n}\n",
|
|
26
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { withDefaults } from \"../../support/withDefaults\";\nimport { redisConfigDefaults, type RedisConfig } from \"./config\";\nimport { RedisManager } from \"./RedisManager\";\n\nexport class RedisServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n RedisManager,\n () =>\n new RedisManager(\n withDefaults(\n redisConfigDefaults(),\n this.app.config.get<RedisConfig>(\"redis\", {}),\n ),\n ),\n );\n }\n\n /**\n * Closes the client, if anything built one. Under `gemi dev` this is what\n * keeps each `bun --hot` reload from leaving a Redis connection behind; see\n * `DatabaseServiceProvider.shutdown`.\n */\n shutdown() {\n if (!this.app.resolved(RedisManager)) return;\n this.app.make(RedisManager).close();\n }\n}\n",
|
|
27
|
-
"import type { ToolContext } from \"../../ai/Agent\";\nimport { ToolError } from \"../../ai/redact\";\nimport { s, type AnySchema, type JSONSchema, type Schema } from \"../../ai/Schema\";\nimport type { RouteSource } from \"../../http/ApiRouter\";\nimport type { HttpRequest } from \"../../http/HttpRequest\";\nimport type {\n McpFileBinder,\n McpMethod,\n McpModelParam,\n McpParamBinder,\n McpResultProjection,\n McpRouteDeclaration,\n McpRouter,\n} from \"../../http/McpRouter\";\nimport type { ApiRouteDispatcher, DispatchCredentials } from \"../router/ApiRouteDispatcher\";\n\n/**\n * Who a tool call runs as. Always an argument to the registry, never read from\n * ambient state by it.\n *\n * `local` is an agent running in this server, and `req` is the request that\n * started its run — read by `toAgentTools` at the moment the tool executes,\n * not when the tool was built, which is what lets one set of tools serve every\n * user's run.\n *\n * That producer *does* read ambient state, since a run no longer carries a\n * request (see `AgentContext`), and the boundary is what keeps that from\n * mattering here: the registry is handed a caller and dispatches as that\n * caller, so a second producer — a job, a test, v2's remote client — supplies\n * one however it likes, and a run with no ambient request is refused by\n * `toAgentTools` before the registry is reached rather than dispatching as\n * nobody.\n * The call is dispatched with that request's credentials, so it runs as that\n * user, through that route's middleware.\n *\n * `remote` is v2's MCP client with a bearer token. It is typed so the seam\n * exists and every entry point already takes it; each of them refuses it,\n * because a token resolver is what makes it safe and there is none yet.\n * Reading the caller from `RequestContext` instead would have been shorter,\n * and would have left v2 a registry that only works inside a gemi request.\n */\nexport type McpCaller =\n | { kind: \"local\"; req: HttpRequest<any, any> }\n | { kind: \"remote\"; token: string };\n\n/**\n * One tool call, as the app's hooks see it: who is calling, the request their\n * identity comes from, which tool, its parsed arguments, and — for a call an\n * agent run makes — the run's tool context.\n *\n * `req` is `caller.req`, repeated so a hook that only wants the request does\n * not have to narrow the caller. `ctx` is absent when `McpRegistry.execute` is\n * called without one (a test, a script dispatching a tool directly).\n */\nexport type McpCallContext = {\n caller: Extract<McpCaller, { kind: \"local\" }>;\n req: HttpRequest<any, any>;\n tool: McpToolDescriptor;\n input: Record<string, unknown>;\n ctx?: ToolContext;\n};\n\n/**\n * What `McpRouter.credentials` answers for a call: the app's own headers and\n * cookies, sent beside gemi's access token. See `DispatchCredentials`.\n */\nexport type McpCredentials = DispatchCredentials;\n\n/**\n * MCP's tool annotations, from the verb. They drive confirmation prompts in\n * remote clients, which is why they are set now rather than when v2 lands:\n * every app would otherwise have to revisit its MCP file to get them.\n *\n * Only what the verb says is set. POST and PATCH carry nothing, and MCP's\n * defaults for an unannotated tool — not read-only, possibly destructive — are\n * the honest answer for them.\n */\nexport type McpToolAnnotations = {\n readOnlyHint?: true;\n destructiveHint?: true;\n idempotentHint?: true;\n};\n\n/**\n * One tool, described without committing to who consumes it.\n *\n * The registry's native output. An in-process agent projects it to an\n * `AgentTool` (`toAgentTools`); v2's `tools/list` will project it to JSON.\n * Emitting `AgentTool`s here instead would leave v2 reversing JSON Schema back\n * out of them.\n */\nexport interface McpToolDescriptor {\n /** The `routes` key, verbatim. See `McpRouter`. */\n readonly name: string;\n readonly description: string;\n readonly method: McpMethod;\n /** The route's path as declared, params unfilled. */\n readonly url: string;\n /**\n * What the model fills in: the declared `input`, plus a string for every\n * `\"input\"` path param and every `\"input\"` file field, and the model-facing\n * field of every `this.param(...)` (#767), under its own name and schema.\n * Bound params and bound files are not in it at all, and neither is the raw\n * name of a param the model names in its own terms.\n */\n readonly inputSchema: Schema<Record<string, unknown>>;\n /**\n * The route meta's `output`: the shape of what the tool answers, after\n * `result`. Absent when the meta declares none. v2's `tools/list` emits it\n * as `outputSchema`.\n */\n readonly outputSchema?: AnySchema;\n readonly annotations: McpToolAnnotations;\n readonly tags: readonly string[];\n readonly requiresApproval: boolean;\n /** The controller method mounted at the route, when there is one (#502). */\n readonly source?: RouteSource;\n}\n\n/**\n * Narrows `list`. Data rather than a predicate, because v2's per-token\n * visibility will come from a token's grants, which are data. Both fields\n * narrow: `names` keeps only those tools, `tags` keeps tools carrying at least\n * one of them.\n */\nexport type McpToolFilter = {\n names?: readonly string[];\n tags?: readonly string[];\n};\n\n/**\n * A failure the model is meant to read: a 4xx from the route, a missing file,\n * a bad argument. v2 turns it into `isError: true` rather than a protocol\n * error. Anything else thrown from `execute` is the caller's mistake or the\n * server's, not the model's.\n */\nexport class McpToolError extends ToolError {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = \"McpToolError\";\n }\n}\n\n/** Tool names as OpenAI's function tools and MCP clients both accept them. */\nconst TOOL_NAME = /^[A-Za-z0-9_-]{1,64}$/;\n\n/** `:name`, with URLPattern's modifiers. */\nconst PATH_PARAM = /:([A-Za-z_][A-Za-z0-9_]*)([?*+]?)/g;\n\n/**\n * A 4xx body goes to the model verbatim, so a validation error can be fixed on\n * the next step — but a route that answers a 4xx with a page of HTML should not\n * be able to fill the context window with it.\n */\nconst MAX_ERROR_BODY = 4000;\n\n/**\n * The same limit for a success, set higher: a list route answering every row,\n * or a route serving a large text file, would otherwise land in the context\n * window whole. Past it the model gets the text cut short and told so, rather\n * than an error — the route has already done what it does, and a model told it\n * failed would do it again.\n */\nconst MAX_RESULT_BODY = 100_000;\n\n/** A body a model can read. A missing content-type is read as text. */\nconst READABLE = /^(text\\/|application\\/([\\w.+-]*\\+)?(json|xml))/i;\n\ntype PathParam = { name: string; modifier: string };\n\ntype Plan = {\n descriptor: McpToolDescriptor;\n params: PathParam[];\n paramBinders: Map<string, McpParamBinder>;\n /** Params the model names in its own terms: the input key, and the resolver. */\n modelParams: Map<string, { key: string; param: McpModelParam<any, any> }>;\n fileFields: { name: string; binder: McpFileBinder | \"input\" }[];\n jsonKeys: string[];\n result?: McpResultProjection;\n};\n\n/**\n * The tools an app's `McpRouter` declares, resolved against the api route\n * table, and the one place a call to them is dispatched.\n *\n * Built once, at boot, from the dispatcher's flat route table — so a route\n * that was renamed or unmounted fails the boot here, and at compile time\n * before that, rather than the first model that calls it.\n *\n * THE INVARIANT: a tool call can do nothing its caller could not do with a\n * direct HTTP request. `execute` builds a request and hands it to\n * `ApiRouteDispatcher.dispatchAs`, which runs the route's middleware, auth and\n * policies exactly as for a client. It never reaches for a flat entry's\n * `exec`, which is the handler alone — no auth, no policies, no rate limit —\n * and is the shorter path for exactly that reason.\n */\nexport class McpRegistry<R extends McpRouter<any> = McpRouter<any>> {\n static token = \"router.mcp\";\n\n /**\n * Types only, never set: the app's router, which a typed `toAgentTools`\n * reads its tools' names, inputs and outputs from (#771). The container\n * answers an untyped registry; name the router where the tools are built:\n * `app(McpRegistry) as McpRegistry<SiteMcpRouter>`.\n */\n declare readonly __router?: R;\n\n private readonly plans = new Map<string, Plan>();\n\n constructor(\n private readonly router: McpRouter<any>,\n private readonly dispatcher: Pick<\n ApiRouteDispatcher,\n \"flatRoutes\" | \"dispatchAs\" | \"getRouteHandlerAndParams\"\n >,\n ) {\n for (const [name, declaration] of Object.entries(router.routes ?? {})) {\n this.plans.set(name, this.plan(name, declaration));\n }\n }\n\n /**\n * The tools `caller` may see. v1 has only local callers, and every declared\n * tool is visible to every one of them; `filter` is taken now so the shape\n * v2's per-token visibility needs is the shape callers already use.\n */\n list(caller: McpCaller, filter?: McpToolFilter): McpToolDescriptor[] {\n assertLocal(caller);\n return this.descriptors(filter);\n }\n\n /**\n * The same listing without a caller, for a projection built once before any\n * caller exists — `toAgentTools`. It is not per-caller visibility, and a v2\n * remote listing must go through `list`.\n */\n descriptors(filter?: McpToolFilter): McpToolDescriptor[] {\n const out: McpToolDescriptor[] = [];\n for (const { descriptor } of this.plans.values()) {\n if (filter?.names && !filter.names.includes(descriptor.name)) continue;\n if (filter?.tags && !descriptor.tags.some((tag) => filter.tags.includes(tag))) continue;\n out.push(descriptor);\n }\n return out;\n }\n\n /**\n * Calls the tool `name` as `caller`, and answers the route's JSON.\n *\n * Before dispatching, the router's `credentials` hook, when it has one, is\n * asked for the app's own headers and cookies for this call; they ride\n * beside the access token (see `McpRouter.credentials`). A throw from it is a\n * server failure, logged for the app, like a binder's.\n *\n * `ctx` is the tool context of the agent call, and only files need it:\n * `\"input\"` files resolve through `ctx.attachments`, the handle already\n * scoped to the caller, and bound files are chosen by a binder given `ctx`.\n * Nothing here reads an attachment any other way — the store has no\n * unscoped lookup, and this must not become one.\n *\n * A 2xx answers its JSON, or its text, cut at `MAX_RESULT_BODY`; a body\n * that is neither — a file a route serves — is described, not shown. A tool\n * whose meta has a `result` or an `output` answers its JSON put through\n * them instead, and is cut after that, not before (see `project`).\n * A 4xx throws an `McpToolError` carrying the route's body, so a validation\n * error reaches the model word for word. Anything else, or a route that\n * throws, throws an `McpToolError` that says only that the server failed.\n * The app gets the reason in its log, and a stack trace or a database\n * message is not something to hand a model that may be steered by whoever\n * wrote the document it is reading.\n */\n async execute(\n caller: McpCaller,\n name: string,\n args: unknown,\n ctx?: ToolContext,\n ): Promise<unknown> {\n assertLocal(caller);\n const plan = this.plans.get(name);\n if (!plan) {\n throw new Error(`McpRegistry: there is no tool named \"${name}\".`);\n }\n const { descriptor } = plan;\n\n const parsed = descriptor.inputSchema.safeParse(args);\n if (parsed.ok === false) {\n throw new McpToolError(`Invalid arguments for \"${name}\": ${parsed.errors.join(\", \")}`);\n }\n const input = parsed.value;\n\n const call: McpCallContext = { caller, req: caller.req, tool: descriptor, input, ctx };\n const path = await this.fillPath(plan, call);\n\n // The dispatcher routes `path` afresh and takes the first route that\n // matches it, so a model's \"archive-all\" for `/products/:id` would reach a\n // `/products/archive-all` declared above it — a route the app never\n // exposed, behind none of this tool's approval or annotations.\n const { path: matched } = this.dispatcher.getRouteHandlerAndParams(\n new Request(`http://gemi.internal/api${path}`),\n );\n if (matched !== descriptor.url) {\n throw new McpToolError(`\"${name}\" has nothing at ${path}.`, 404);\n }\n\n const json: Record<string, unknown> = {};\n for (const key of plan.jsonKeys) {\n if (input[key] !== undefined) json[key] = input[key];\n }\n\n let body: FormData | Record<string, unknown> | undefined;\n let query = \"\";\n if (plan.fileFields.length > 0) {\n body = await this.formData(plan, json, call);\n } else if (descriptor.method === \"GET\") {\n query = toQuery(json);\n } else if (plan.jsonKeys.length > 0) {\n body = json;\n }\n\n const credentials = this.router.credentials\n ? await this.bind(plan, \"the credentials\", () => this.router.credentials!(call))\n : undefined;\n\n let response: Response;\n try {\n const target = query ? `${path}?${query}` : path;\n response = credentials\n ? await this.dispatcher.dispatchAs(caller.req, descriptor.method, target, body, {\n credentials,\n })\n : await this.dispatcher.dispatchAs(caller.req, descriptor.method, target, body);\n } catch (error) {\n // What a client would get as a 500. A handler's throw is logged by the\n // dispatcher too, but dispatchAs's own refusal of the path is not.\n console.error(`[gemi/mcp] Dispatching \"${name}\" failed:`, error);\n throw new McpToolError(`\"${name}\" failed on the server.`, 500);\n }\n if (!plan.result && !descriptor.outputSchema) {\n return await readResponse(name, response);\n }\n return await this.project(plan, call, response);\n }\n\n // --- building ------------------------------------------------------------\n\n private plan(name: string, declaration: McpRouteDeclaration): Plan {\n if (!TOOL_NAME.test(name)) {\n throw new Error(\n `McpRouter: \"${name}\" 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 — letters, digits, \"_\" and \"-\", at most 64.`,\n );\n }\n const where = `McpRouter: \"${name}\" (${declaration?.method} ${declaration?.url})`;\n if (!(declaration instanceof Object) || declaration.__internal_brand !== \"McpRoute\") {\n throw new Error(\n `McpRouter: \"${name}\" is not a route. Declare it with this.fromApiRoute(...).`,\n );\n }\n const { method, url, meta } = declaration;\n if (url.startsWith(\"/__gemi__\")) {\n throw new Error(`${where} is a framework route, not an app route.`);\n }\n\n const entry = this.dispatcher.flatRoutes[url]?.[method];\n if (!entry) {\n throw new Error(\n `${where} names no route of this app. Expose a route by the path and verb it is mounted at.`,\n );\n }\n\n // Mirrors the compile-time check, for a router written in JavaScript or\n // behind a cast: a param left undeclared would otherwise be the model's.\n const params: PathParam[] = [...url.matchAll(PATH_PARAM)].map(([, param, modifier]) => ({\n name: param,\n modifier,\n }));\n const declared = meta.params ?? {};\n for (const param of params) {\n if (!(param.name in declared)) {\n throw new Error(\n `${where}: the path param \"${param.name}\" must be bound or declared \"input\".`,\n );\n }\n }\n for (const [key, binder] of Object.entries(declared)) {\n if (!params.some((param) => param.name === key)) {\n throw new Error(`${where}: \"${key}\" in params is not a param of the url.`);\n }\n if (isModelParam(binder)) {\n assertModelParam(where, `params.${key}`, binder);\n } else {\n assertBinder(where, `params.${key}`, binder);\n }\n }\n\n let jsonKeys: string[] = [];\n let base: JSONSchema | undefined;\n if (meta.input) {\n base = meta.input.toJSONSchema();\n if (base.type !== \"object\" || !base.properties) {\n throw new Error(\n `${where}: input must be an s.object(...), the fields of the request body.`,\n );\n }\n jsonKeys = Object.keys(base.properties);\n }\n\n const fileFields = Object.entries(meta.files ?? {}).map(([field, binder]) => ({\n name: field,\n binder,\n }));\n for (const file of fileFields) {\n assertBinder(where, `files.${file.name}`, file.binder);\n }\n if (meta.result !== undefined && typeof meta.result !== \"function\") {\n throw new Error(`${where}: result must be a function.`);\n }\n if (meta.output !== undefined && typeof meta.output?.safeParse !== \"function\") {\n throw new Error(`${where}: output must be a schema, built with s.`);\n }\n if (fileFields.length > 0 && method === \"GET\") {\n throw new Error(`${where}: a GET has no body to carry a file.`);\n }\n\n const extras: Record<string, AnySchema> = {};\n // Two params, or a param and a file, can name the same input key only by\n // a `this.param` `as`; the model could send one value for both.\n const addExtra = (key: string, schema: AnySchema) => {\n if (key in extras) {\n throw new Error(\n `${where}: \"${key}\" is the input key of two params or files, and the model can only send one.`,\n );\n }\n extras[key] = schema;\n };\n const paramBinders = new Map<string, McpParamBinder>();\n const modelParams = new Map<string, { key: string; param: McpModelParam<any, any> }>();\n for (const param of params) {\n const binder = declared[param.name];\n if (binder === \"input\") {\n const field = s.string().describe(`The \":${param.name}\" segment of the url.`);\n addExtra(\n param.name,\n param.modifier === \"?\" || param.modifier === \"*\" ? field.optional() : field,\n );\n } else if (isModelParam(binder)) {\n const key = binder.as ?? param.name;\n addExtra(key, binder.input);\n modelParams.set(param.name, { key, param: binder });\n } else {\n paramBinders.set(param.name, binder);\n }\n }\n for (const file of fileFields) {\n if (file.binder === \"input\") {\n addExtra(\n file.name,\n s\n .string()\n .describe(\n \"The id of an attachment the user uploaded or a tool produced (gemi_att_…), whose file is sent as this field.\",\n ),\n );\n }\n }\n // A bound file is not the model's, but it shares the form with the input,\n // so it collides all the same.\n const bodyKeys = [...Object.keys(extras), ...fileFields.map((file) => file.name)];\n for (const key of new Set(bodyKeys)) {\n if (jsonKeys.includes(key)) {\n throw new Error(\n `${where}: \"${key}\" is both an input field and a param or file, and the model can only send one.`,\n );\n }\n }\n\n return {\n descriptor: Object.freeze({\n name,\n description: meta.description,\n method,\n url,\n inputSchema: combineSchemas(meta.input, base, s.object(extras)),\n ...(meta.output ? { outputSchema: meta.output } : {}),\n annotations: annotationsFor(method),\n tags: Object.freeze([...(meta.tags ?? [])]),\n requiresApproval: meta.requiresApproval === true,\n ...(entry.source ? { source: entry.source } : {}),\n }),\n params,\n paramBinders,\n modelParams,\n fileFields,\n jsonKeys,\n ...(meta.result ? { result: meta.result } : {}),\n };\n }\n\n // --- calling -------------------------------------------------------------\n\n private async fillPath(plan: Plan, call: McpCallContext): Promise<string> {\n const { input, req } = call;\n let path = plan.descriptor.url;\n for (const param of plan.params) {\n const binder = plan.paramBinders.get(param.name);\n const modelParam = plan.modelParams.get(param.name);\n let value: unknown;\n if (binder || modelParam) {\n value = modelParam\n ? await this.bind(\n plan,\n `the param \"${param.name}\"`,\n () => modelParam.param.bind(input[modelParam.key], req, call),\n { refusals: true },\n )\n : await this.bind(plan, `the param \"${param.name}\"`, () => binder!(req, call));\n if (value === undefined || value === null || value === \"\") {\n console.error(\n `[gemi/mcp] The ${modelParam ? \"resolver\" : \"binder\"} for \"${param.name}\" of \"${plan.descriptor.name}\" returned ${JSON.stringify(value)}.`,\n );\n throw new McpToolError(`\"${plan.descriptor.name}\" failed on the server.`, 500);\n }\n } else {\n value = input[param.name];\n }\n\n const token = `:${param.name}${param.modifier}`;\n if (value === undefined || value === null || value === \"\") {\n if (param.modifier !== \"?\" && param.modifier !== \"*\") {\n throw new McpToolError(`\"${param.name}\" of \"${plan.descriptor.name}\" must not be empty.`);\n }\n path = path.replace(`/${token}`, \"\");\n continue;\n }\n path = path.replace(token, encodeSegment(plan.descriptor.name, param, String(value)));\n }\n return path;\n }\n\n private async formData(\n plan: Plan,\n json: Record<string, unknown>,\n call: McpCallContext,\n ): Promise<FormData> {\n const { input, ctx } = call;\n const name = plan.descriptor.name;\n if (!ctx) {\n throw new Error(\n `McpRegistry: \"${name}\" sends a file, which is resolved through the tool context's attachments, and none was passed to execute().`,\n );\n }\n const form = new FormData();\n for (const [key, value] of Object.entries(json)) {\n appendField(form, key, value);\n }\n for (const field of plan.fileFields) {\n const id =\n field.binder === \"input\"\n ? (input[field.name] as string)\n : await this.bind(plan, `the file \"${field.name}\"`, () =>\n (field.binder as McpFileBinder)(ctx, call),\n );\n if (typeof id !== \"string\" || id === \"\") {\n throw new McpToolError(\n `\"${name}\" needs a file for \"${field.name}\", and this turn has none. Ask the user to attach one.`,\n );\n }\n // Scoped to the caller: someone else's id is a not-found, worded like an\n // invented one, and that error is what the model reads.\n form.append(field.name, await ctx.attachments.file(id));\n }\n return form;\n }\n\n /**\n * A 2xx put through the meta's `result` and `output`, for a tool that has\n * either.\n *\n * The whole body is parsed and projected before anything is cut: trimming a\n * large answer down is what a projection is for, and cutting first would\n * hand `result` half a JSON document. The projected value is then held to\n * `MAX_RESULT_BODY` the way any answer is.\n *\n * Every failure here is the app's, not the model's — a route that answered\n * something other than JSON, a `result` that threw, an answer `output` does\n * not describe — so each is logged and the model is told only that the\n * tool failed. Telling it more would invite a retry of a call that already\n * did what it does.\n */\n private async project(plan: Plan, call: McpCallContext, response: Response): Promise<unknown> {\n const { name, outputSchema } = plan.descriptor;\n if (response.status < 200 || response.status >= 300) {\n return await readResponse(name, response);\n }\n const failed = (why: string, error?: unknown) => {\n if (error === undefined) console.error(`[gemi/mcp] \"${name}\" ${why}`);\n else console.error(`[gemi/mcp] \"${name}\" ${why}`, error);\n return new McpToolError(`\"${name}\" failed on the server.`, 500);\n };\n\n const type = response.headers.get(\"Content-Type\");\n const text = await response.text();\n let data: unknown = null;\n if (text !== \"\") {\n try {\n data = JSON.parse(text);\n } catch {\n throw failed(\n `answered ${type ?? \"a body with no content-type\"} that is not JSON, and its result or output needs JSON.`,\n );\n }\n }\n\n let value = data;\n if (plan.result) {\n try {\n value = await plan.result(data, call);\n } catch (error) {\n throw failed(\"failed in its result projection:\", error);\n }\n }\n if (outputSchema) {\n const parsed = outputSchema.safeParse(value);\n if (parsed.ok === false) {\n throw failed(`answered what its output schema refuses: ${parsed.errors.join(\", \")}`);\n }\n value = parsed.value;\n }\n\n if (value === undefined) return null;\n const serialised = JSON.stringify(value);\n if (serialised !== undefined && serialised.length > MAX_RESULT_BODY) {\n return `${serialised.slice(0, MAX_RESULT_BODY)}… [cut at ${MAX_RESULT_BODY} of ${serialised.length} characters]`;\n }\n return value;\n }\n\n /**\n * Runs an app's binder. Its failure is the server's, not the model's, so the\n * model is told only that; the app gets the error in its log.\n *\n * With `refusals`, a `ToolError` (an `McpToolError` is one) is the app\n * refusing what the model sent, and is passed on for the model to read. Only\n * a model-facing param's resolver gets that: it is translating the model's\n * own words, so \"there is no such page\" is something the model can fix. A\n * plain binder reads nothing of the model's, and its throw stays the\n * server's.\n */\n private async bind<T>(\n plan: Plan,\n what: string,\n fn: () => T | Promise<T>,\n options: { refusals?: boolean } = {},\n ): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n if (options.refusals && error instanceof ToolError) throw error;\n console.error(`[gemi/mcp] Binding ${what} of \"${plan.descriptor.name}\" failed:`, error);\n throw new McpToolError(`\"${plan.descriptor.name}\" failed on the server.`, 500);\n }\n }\n}\n\nfunction assertLocal(caller: McpCaller): asserts caller is Extract<McpCaller, { kind: \"local\" }> {\n if (caller?.kind !== \"local\") {\n throw new Error(\n \"McpRegistry: only local callers are implemented. A remote caller needs a bearer-token resolver, which v1 does not have — and until it does there is no remote surface to call from.\",\n );\n }\n}\n\n/** A binder is a function or `\"input\"`; anything else would leave the value to nobody. */\nfunction assertBinder(where: string, at: string, binder: unknown) {\n if (binder !== \"input\" && typeof binder !== \"function\") {\n throw new Error(`${where}: ${at} must be a function or \"input\".`);\n }\n}\n\n/**\n * By brand, as a declaration is, rather than `instanceof`: an app that ends up\n * with two copies of gemi would otherwise see its params as broken binders.\n */\nfunction isModelParam(value: unknown): value is McpModelParam<any, any> {\n return value instanceof Object && (value as McpModelParam).__internal_brand === \"McpModelParam\";\n}\n\n/** A `this.param(...)`, checked for a router written in JavaScript or behind a cast. */\nfunction assertModelParam(where: string, at: string, param: McpModelParam<any, any>) {\n if (param.as !== undefined && (typeof param.as !== \"string\" || param.as === \"\")) {\n throw new Error(`${where}: ${at}.as must be a non-empty string.`);\n }\n const schema = param.input;\n if (typeof schema?.safeParse !== \"function\" || typeof schema.toJSONSchema !== \"function\") {\n throw new Error(`${where}: ${at}.input must be a schema, built with s.`);\n }\n if (typeof param.bind !== \"function\") {\n throw new Error(`${where}: ${at}.bind must be a function.`);\n }\n}\n\nfunction annotationsFor(method: McpMethod): McpToolAnnotations {\n switch (method) {\n case \"GET\":\n return Object.freeze({ readOnlyHint: true as const });\n case \"DELETE\":\n return Object.freeze({ destructiveHint: true as const });\n case \"PUT\":\n return Object.freeze({ idempotentHint: true as const });\n default:\n return Object.freeze({});\n }\n}\n\n/**\n * One schema out of two: the app's `input`, and the strings for `\"input\"`\n * params and files.\n *\n * Built by hand, like `questionSchema` in `ai/Agent.ts`, rather than by\n * widening `s` with a merge: `Schema.ts` is deliberately the strict\n * structured-output subset and owns no operation on a finished schema. Both\n * halves are real `s` schemas, each parses the whole value and drops the keys\n * that are not its own — which is also where a bound param the model sent\n * anyway disappears, since neither half declares it.\n */\nfunction combineSchemas(\n input: AnySchema | undefined,\n base: JSONSchema | undefined,\n extras: AnySchema,\n): Schema<Record<string, unknown>> {\n const extra = extras.toJSONSchema();\n const json: JSONSchema = {\n ...(base ?? {}),\n type: \"object\",\n properties: { ...(base?.properties ?? {}), ...extra.properties },\n required: [...(base?.required ?? []), ...(extra.required ?? [])],\n additionalProperties: false,\n };\n\n const safeParse = (value: unknown) => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { ok: false as const, errors: [\"expected an object\"] };\n }\n const errors: string[] = [];\n let out: Record<string, unknown> = {};\n if (input) {\n const own = input.safeParse(value);\n if (own.ok === false) errors.push(...own.errors);\n else out = { ...own.value };\n }\n const more = extras.safeParse(value);\n if (more.ok === false) errors.push(...more.errors);\n else out = { ...out, ...more.value };\n return errors.length > 0 ? { ok: false as const, errors } : { ok: true as const, value: out };\n };\n\n return {\n toJSONSchema: () => json,\n safeParse,\n parse(value: unknown) {\n const result = safeParse(value);\n if (result.ok === false) throw new Error(result.errors.join(\", \"));\n return result.value;\n },\n } as unknown as Schema<Record<string, unknown>>;\n}\n\n/**\n * A model-supplied param, as one encoded path segment. `.` and `..` are\n * refused rather than encoded: they encode to themselves, and URL parsing\n * would walk them out of the route.\n */\nfunction encodeSegment(tool: string, param: PathParam, value: string): string {\n const parts = param.modifier === \"*\" || param.modifier === \"+\" ? value.split(\"/\") : [value];\n if (parts.some((part) => part === \".\" || part === \"..\")) {\n throw new McpToolError(`\"${value}\" is not a valid value for \"${param.name}\" of \"${tool}\".`);\n }\n return parts.map(encodeURIComponent).join(\"/\");\n}\n\n/**\n * A JSON field as a multipart form field. A form has only strings, so the\n * route reads `\"12\"` where a JSON body would have carried `12` — what a\n * browser form sends the same route. An array becomes the key repeated, which\n * `HttpRequest` reads back as an array.\n */\nfunction appendField(form: FormData, key: string, value: unknown) {\n if (value === undefined || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) appendField(form, key, item);\n return;\n }\n form.append(key, typeof value === \"object\" ? JSON.stringify(value) : String(value));\n}\n\n/** A GET's input, as the query string `HttpRequest.search` reads. */\nfunction toQuery(json: Record<string, unknown>): string {\n const search = new URLSearchParams();\n const add = (key: string, value: unknown) => {\n if (value === undefined || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) add(key, item);\n return;\n }\n search.append(key, typeof value === \"object\" ? JSON.stringify(value) : String(value));\n };\n for (const [key, value] of Object.entries(json)) add(key, value);\n return search.toString();\n}\n\nasync function readResponse(name: string, response: Response): Promise<unknown> {\n if (response.status >= 200 && response.status < 300) {\n const type = response.headers.get(\"Content-Type\");\n if (type && !READABLE.test(type)) {\n const size = (await response.arrayBuffer()).byteLength;\n return `\"${name}\" answered with ${size} bytes of ${type}, which is not shown.`;\n }\n const text = await response.text();\n if (text === \"\") return null;\n if (text.length > MAX_RESULT_BODY) {\n return `${text.slice(0, MAX_RESULT_BODY)}… [cut at ${MAX_RESULT_BODY} of ${text.length} characters]`;\n }\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n const text = await response.text();\n if (response.status >= 400 && response.status < 500) {\n const shown = text.length > MAX_ERROR_BODY ? `${text.slice(0, MAX_ERROR_BODY)}…` : text;\n throw new McpToolError(\n `\"${name}\" was refused with ${response.status}${shown ? `: ${shown}` : \".\"}`,\n response.status,\n );\n }\n if (response.status < 500) {\n // A redirect, or a 1xx: nothing the model can follow, and nothing the\n // dispatcher logged.\n console.error(\n `[gemi/mcp] \"${name}\" answered ${response.status}${response.headers.get(\"Location\") ? ` to ${response.headers.get(\"Location\")}` : \"\"}, which a tool call cannot follow.`,\n );\n }\n throw new McpToolError(`\"${name}\" failed on the server.`, response.status);\n}\n",
|
|
28
|
-
"import { ServiceProvider } from \"../../support/ServiceProvider\";\nimport { McpRegistry } from \"../mcp/McpRegistry\";\nimport { ApiRouteDispatcher } from \"./ApiRouteDispatcher\";\nimport { DomainRouter } from \"./DomainRouter\";\nimport { ViewRouteDispatcher } from \"./ViewRouteDispatcher\";\nimport type { ApiRouteConfig, DomainsConfig, McpRouteConfig, ViewRouteConfig } from \"./config\";\nimport type { Repository } from \"../../support/Repository\";\n\nfunction readConfig<T>(config: Repository, key: string): T {\n const slice = config.get<T>(key);\n if (!slice) {\n throw new Error(\n `Missing \"${key}\" configuration. Route dispatching needs a root router — declare it in \"app/config/route.ts\".`,\n );\n }\n return slice;\n}\n\n/**\n * Owns both route dispatchers. Laravel ships a single RouteServiceProvider for\n * every route file it loads; the api/view split is a config slice, not a\n * separate provider.\n */\nexport class RouteServiceProvider extends ServiceProvider {\n register() {\n this.app.singleton(\n ApiRouteDispatcher,\n () =>\n new ApiRouteDispatcher(\n readConfig<ApiRouteConfig>(this.app.config, \"route.api\"),\n ),\n );\n\n this.app.singleton(\n ViewRouteDispatcher,\n () =>\n new ViewRouteDispatcher(\n readConfig<ViewRouteConfig>(this.app.config, \"route.view\"),\n ),\n );\n\n // The root pair above is the root group's; the other `route.domains`\n // groups get their own only when they bring their own routers.\n this.app.singleton(\n DomainRouter,\n () =>\n new DomainRouter(\n { api: this.app.make(ApiRouteDispatcher), view: this.app.make(ViewRouteDispatcher) },\n {\n api: readConfig<ApiRouteConfig>(this.app.config, \"route.api\"),\n view: readConfig<ViewRouteConfig>(this.app.config, \"route.view\"),\n domains: this.app.config.get<DomainsConfig | undefined>(\"route.domains\"),\n },\n ),\n );\n\n // Bound whether or not the app declares one, so asking for it without\n // `route.mcp` fails with a sentence rather than an unbound token.\n this.app.singleton(McpRegistry, () => {\n const config = this.app.config.get<McpRouteConfig | undefined>(\"route.mcp\");\n if (!config?.router) {\n throw new Error(\n 'No MCP router is configured. Declare one as `mcp: { router }` in \"app/config/route.ts\".',\n );\n }\n return new McpRegistry(new config.router(), this.app.make(ApiRouteDispatcher));\n });\n }\n\n /**\n * The dispatchers of every host group, and the MCP registry when an app declares one, must\n * not be built lazily: constructing the dispatchers flattens the route\n * tables and runs `assertNoReservedRoutePaths`, and constructing the registry\n * resolves every tool against those tables. A missing `route` config, a\n * route under a reserved prefix or a tool naming a route that is gone has to\n * fail the boot rather than the first request, or the first tool call, that\n * happens to hit it.\n */\n boot() {\n this.app.make(ApiRouteDispatcher);\n this.app.make(ViewRouteDispatcher);\n // Builds every host group's dispatchers and validates `route.domains`.\n this.app.make(DomainRouter);\n if (this.app.config.get(\"route.mcp\")) {\n this.app.make(McpRegistry);\n }\n }\n}\n"
|
|
29
|
-
],
|
|
30
|
-
"mappings": "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",
|
|
31
|
-
"debugId": "99432915D07BB43864756E2164756E21",
|
|
32
|
-
"names": []
|
|
33
|
-
}
|