@mastra/deployer 1.79.0-alpha.1 → 1.79.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/_types/@mastra_hono/dist/browser-stream/index.d.ts +54 -2
  2. package/dist/_types/@mastra_hono/dist/index.d.ts +1 -0
  3. package/dist/{analyze-CcyVW_jJ.cjs → analyze-CPVTLGuy.cjs} +168 -25
  4. package/dist/analyze-CPVTLGuy.cjs.map +1 -0
  5. package/dist/{analyze-BP0cLyWH.js → analyze-Dvzg7NFK.js} +165 -22
  6. package/dist/analyze-Dvzg7NFK.js.map +1 -0
  7. package/dist/build/analyze.cjs +1 -1
  8. package/dist/build/analyze.js +1 -1
  9. package/dist/build/fs-routing/discover.d.ts.map +1 -1
  10. package/dist/build/index.cjs +4 -4
  11. package/dist/build/index.js +4 -4
  12. package/dist/build/shared/extract-mastra-option.d.ts.map +1 -1
  13. package/dist/{build-DGJW5-aB.cjs → build-Bdncb817.cjs} +71 -9
  14. package/dist/build-Bdncb817.cjs.map +1 -0
  15. package/dist/{build-CNYwIKPG.js → build-C8gia-n5.js} +71 -8
  16. package/dist/build-C8gia-n5.js.map +1 -0
  17. package/dist/bundler/find-workspaces.d.ts +52 -0
  18. package/dist/bundler/find-workspaces.d.ts.map +1 -0
  19. package/dist/bundler/index.cjs +3 -3
  20. package/dist/bundler/index.js +3 -3
  21. package/dist/bundler/workspaceDependencies.d.ts.map +1 -1
  22. package/dist/{bundlerOptions-BtMp_x-T.cjs → bundlerOptions-BA9vxjZt.cjs} +51 -1
  23. package/dist/bundlerOptions-BA9vxjZt.cjs.map +1 -0
  24. package/dist/{bundlerOptions-ID9i-l8I.js → bundlerOptions-DpvO59QF.js} +49 -1
  25. package/dist/bundlerOptions-DpvO59QF.js.map +1 -0
  26. package/dist/docs/SKILL.md +1 -1
  27. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  28. package/dist/index.cjs +3 -3
  29. package/dist/index.js +3 -3
  30. package/dist/server/index.cjs +243 -4
  31. package/dist/server/index.cjs.map +1 -1
  32. package/dist/server/index.d.ts.map +1 -1
  33. package/dist/server/index.js +244 -5
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/services/deps.d.ts +9 -0
  36. package/dist/services/deps.d.ts.map +1 -1
  37. package/dist/services/index.cjs +1 -1
  38. package/dist/services/index.js +1 -1
  39. package/dist/{services-D_V7afWX.cjs → services-BQNEW5XJ.cjs} +126 -1
  40. package/dist/services-BQNEW5XJ.cjs.map +1 -0
  41. package/dist/{services-DXqmEy8F.js → services-PUoAjxMD.js} +115 -2
  42. package/dist/services-PUoAjxMD.js.map +1 -0
  43. package/package.json +8 -9
  44. package/dist/analyze-BP0cLyWH.js.map +0 -1
  45. package/dist/analyze-CcyVW_jJ.cjs.map +0 -1
  46. package/dist/build-CNYwIKPG.js.map +0 -1
  47. package/dist/build-DGJW5-aB.cjs.map +0 -1
  48. package/dist/bundlerOptions-BtMp_x-T.cjs.map +0 -1
  49. package/dist/bundlerOptions-ID9i-l8I.js.map +0 -1
  50. package/dist/services-DXqmEy8F.js.map +0 -1
  51. package/dist/services-D_V7afWX.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKhE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAmB5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAoBnD,KAAK,QAAQ,GAAG,YAAY,CAAC;AAE7B,KAAK,SAAS,GAAG,aAAa,GAAG;IAC/B,OAAO,EAAE,GAAG,CAAC;QAAE,UAAU,EAAE,+BAA+B,CAAA;KAAE,CAAC,CAAC;CAC/D,CAAC;AA8CF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,wCAmB/D;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAER;cAegC,QAAQ;eAAa,SAAS;2CAmbhE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAmC,mDAmMlG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKhE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAmB5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAoBnD,KAAK,QAAQ,GAAG,YAAY,CAAC;AAE7B,KAAK,SAAS,GAAG,aAAa,GAAG;IAC/B,OAAO,EAAE,GAAG,CAAC;QAAE,UAAU,EAAE,+BAA+B,CAAA;KAAE,CAAC,CAAC;CAC/D,CAAC;AA8CF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,wCAmB/D;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAER;cAegC,QAAQ;eAAa,SAAS;2CA4bhE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAmC,mDAmMlG"}
@@ -18,8 +18,8 @@ import util from "util";
18
18
  import { Buffer as Buffer$1 } from "buffer";
19
19
  import { bodyLimit } from "hono/body-limit";
20
20
  import { stream } from "hono/streaming";
21
- import "@mastra/core/request-context";
22
- import { findMatchingCustomRoute } from "@mastra/server/auth";
21
+ import { RequestContext } from "@mastra/core/request-context";
22
+ import { coreAuthMiddleware, findMatchingCustomRoute, isCustomRoutePublic } from "@mastra/server/auth";
23
23
  import { ViewerRegistry, handleInputMessage } from "@mastra/server/browser-stream";
24
24
  import { InMemoryTaskStore } from "@mastra/server/a2a/store";
25
25
  import { Hono } from "hono";
@@ -3196,6 +3196,191 @@ function propagateClientDisconnect(fetchResponse, res) {
3196
3196
  headers: fetchResponse.headers
3197
3197
  });
3198
3198
  }
3199
+ function createAuthMiddleware({ mastra, requiresAuth = true }) {
3200
+ return async (c, next) => {
3201
+ if (!requiresAuth) return next();
3202
+ const authConfig = mastra.getServer()?.auth;
3203
+ if (!authConfig) return next();
3204
+ const requestContext = c.get("requestContext") ?? new RequestContext();
3205
+ c.set("requestContext", requestContext);
3206
+ c.set("mastra", c.get("mastra") ?? mastra);
3207
+ const path = c.req.path;
3208
+ const method = c.req.method;
3209
+ const customRouteAuthConfig = new Map(c.get("customRouteAuthConfig") ?? []);
3210
+ if (!isCustomRoutePublic(path, method, customRouteAuthConfig)) customRouteAuthConfig.set(`${method}:${path}`, true);
3211
+ const authHeader = c.req.header("Authorization");
3212
+ let token = authHeader ? authHeader.replace("Bearer ", "") : null;
3213
+ if (!token) token = c.req.query("apiKey") || null;
3214
+ const result = await coreAuthMiddleware({
3215
+ path,
3216
+ method,
3217
+ getHeader: (name) => c.req.header(name),
3218
+ mastra,
3219
+ authConfig,
3220
+ customRouteAuthConfig,
3221
+ requestContext,
3222
+ rawRequest: c.req.raw,
3223
+ token,
3224
+ buildAuthorizeContext: () => c
3225
+ });
3226
+ for (const [key, value] of Object.entries(result.headers ?? {})) c.header(key, value, key.toLowerCase() === "set-cookie" ? { append: true } : void 0);
3227
+ if (result.action === "next") return next();
3228
+ return c.json(result.body, result.status);
3229
+ };
3230
+ }
3231
+ const TICKET_BYTES = 32;
3232
+ /**
3233
+ * How long an issued ticket stays valid. A ticket is minted immediately before
3234
+ * the socket is opened, so this only has to cover the round trip plus the
3235
+ * handshake — short enough that a ticket leaked from a log is useless by the
3236
+ * time anyone can act on it.
3237
+ */
3238
+ const UPGRADE_TICKET_TTL_MS = 3e4;
3239
+ /**
3240
+ * Ceiling on tracked per-agent issuance counters, so the limiter can't grow
3241
+ * without bound as agent ids are invented. Counters are evicted oldest first.
3242
+ */
3243
+ const MAX_TRACKED_AGENTS = 500;
3244
+ /** 256 bits of base64url, so the ticket can't be guessed or enumerated. */
3245
+ function createTicketValue() {
3246
+ const bytes = new Uint8Array(TICKET_BYTES);
3247
+ globalThis.crypto.getRandomValues(bytes);
3248
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3249
+ }
3250
+ /**
3251
+ * Short-lived, single-use credentials for browser WebSocket upgrades.
3252
+ *
3253
+ * A browser cannot set an `Authorization` header on a WebSocket handshake, so a
3254
+ * deployment that authenticates with a header token would otherwise have to put
3255
+ * that reusable token in the handshake URL, where proxies and access logs can
3256
+ * retain it. Instead the client asks an authenticated HTTP route for a ticket,
3257
+ * and the ticket — worthless once spent, and scoped to one agent and thread —
3258
+ * travels in the URL instead.
3259
+ *
3260
+ * In-process, like the `ViewerRegistry` and the browser sessions it serves: a
3261
+ * screencast is bound to the process holding the browser, so a ticket store
3262
+ * shared across replicas would buy nothing. A ticket presented to a different
3263
+ * replica fails for the same reason the stream itself would.
3264
+ *
3265
+ * Issuance is bounded so the mint endpoint can't be turned into memory or CPU
3266
+ * exhaustion: tickets that are never spent live at most one TTL, a full store
3267
+ * rejects instead of growing, and each agent has a request allowance. Expiry is
3268
+ * swept from the front of the insertion-ordered map, so cleanup costs the
3269
+ * number of entries actually expired rather than a scan of every outstanding
3270
+ * ticket per request.
3271
+ */
3272
+ var UpgradeTicketStore = class {
3273
+ #tickets = /* @__PURE__ */ new Map();
3274
+ #issuance = /* @__PURE__ */ new Map();
3275
+ #now;
3276
+ constructor(now = () => Date.now()) {
3277
+ this.#now = now;
3278
+ }
3279
+ /**
3280
+ * Issue a ticket bound to one agent and thread. Returns `{ ok: false }` when
3281
+ * the agent exceeded its allowance or the store is at capacity.
3282
+ */
3283
+ issue(agentId, threadId, ttlMs = UPGRADE_TICKET_TTL_MS) {
3284
+ const now = this.#now();
3285
+ this.#prune(now);
3286
+ if (this.#tickets.size >= 500) return {
3287
+ ok: false,
3288
+ reason: "capacity"
3289
+ };
3290
+ const window = this.#issuance.get(agentId);
3291
+ if (!window || now - window.windowStart >= 6e4) this.#trackIssuance(agentId, {
3292
+ windowStart: now,
3293
+ count: 0
3294
+ });
3295
+ else if (window.count >= 30) return {
3296
+ ok: false,
3297
+ reason: "rate-limited"
3298
+ };
3299
+ this.#issuance.get(agentId).count += 1;
3300
+ const ticket = createTicketValue();
3301
+ this.#tickets.set(ticket, {
3302
+ agentId,
3303
+ threadId,
3304
+ expiresAt: now + ttlMs
3305
+ });
3306
+ return {
3307
+ ok: true,
3308
+ ticket,
3309
+ expiresIn: Math.ceil(ttlMs / 1e3)
3310
+ };
3311
+ }
3312
+ /**
3313
+ * Spend a ticket. Returns `true` only when the ticket is unexpired and was
3314
+ * issued for this exact agent and thread, in which case it is consumed and a
3315
+ * second presentation fails.
3316
+ */
3317
+ consume(ticket, agentId, threadId) {
3318
+ const entry = this.#tickets.get(ticket);
3319
+ if (!entry) return false;
3320
+ if (entry.expiresAt <= this.#now()) return false;
3321
+ if (entry.agentId !== agentId) return false;
3322
+ if ((entry.threadId ?? void 0) !== (threadId ?? void 0)) return false;
3323
+ this.#tickets.delete(ticket);
3324
+ return true;
3325
+ }
3326
+ /**
3327
+ * Drop expired tickets from the front of the insertion-ordered map. Tickets
3328
+ * are inserted in issue order and share a TTL, so the entries after the first
3329
+ * live one are live too and the sweep can stop there.
3330
+ */
3331
+ #prune(now) {
3332
+ for (const [ticket, entry] of this.#tickets) {
3333
+ if (entry.expiresAt > now) break;
3334
+ this.#tickets.delete(ticket);
3335
+ }
3336
+ }
3337
+ /** Record an issuance window, evicting the oldest counter when at the cap. */
3338
+ #trackIssuance(agentId, window) {
3339
+ this.#issuance.delete(agentId);
3340
+ this.#issuance.set(agentId, window);
3341
+ if (this.#issuance.size > MAX_TRACKED_AGENTS) {
3342
+ const oldest = this.#issuance.keys().next().value;
3343
+ if (oldest !== void 0) this.#issuance.delete(oldest);
3344
+ }
3345
+ }
3346
+ };
3347
+ const TICKET_BODY_MAX_BYTES = 4 * 1024;
3348
+ /**
3349
+ * Resolve an `allowedOrigins` value against the request's `Origin` header.
3350
+ *
3351
+ * Mirrors Hono's `cors.origin` semantics so the same config value means the same
3352
+ * thing on the upgrade as it does on the HTTP routes: `'*'` allows everything,
3353
+ * array entries match literally, and a callback is only an approval when it
3354
+ * returns the request's own origin (the value Hono would echo back) or `'*'`.
3355
+ */
3356
+ async function isOriginAllowed(allowedOrigins, origin, c) {
3357
+ if (Array.isArray(allowedOrigins)) return allowedOrigins.includes(origin);
3358
+ if (typeof allowedOrigins === "function") {
3359
+ const allowed = await allowedOrigins(origin, c);
3360
+ return allowed === origin || allowed === "*";
3361
+ }
3362
+ return allowedOrigins === "*" || allowedOrigins === origin;
3363
+ }
3364
+ /**
3365
+ * Is the `Origin` header the server's own origin?
3366
+ *
3367
+ * A Studio served by this server connects back to the same host, and operators
3368
+ * don't add their own origin to a CORS allowlist because same-origin requests
3369
+ * never needed CORS. Comparing hosts (rather than scheme + host) keeps that
3370
+ * working behind TLS-terminating proxies, where the server sees `http` while the
3371
+ * browser sends `https`.
3372
+ */
3373
+ function isSameOrigin(origin, requestHost) {
3374
+ if (!requestHost) return false;
3375
+ let originHost;
3376
+ try {
3377
+ originHost = new URL(origin).host;
3378
+ } catch {
3379
+ return false;
3380
+ }
3381
+ const host = (requestHost.split(",")[0] ?? "").trim();
3382
+ return originHost !== "" && originHost.toLowerCase() === host.toLowerCase();
3383
+ }
3199
3384
  /**
3200
3385
  * Set up WebSocket-based browser stream endpoint for real-time screencast viewing.
3201
3386
  *
@@ -3205,6 +3390,17 @@ function propagateClientDisconnect(fetchResponse, res) {
3205
3390
  * - Broadcasts frames to all connected viewers
3206
3391
  * - Stops screencast when last viewer disconnects
3207
3392
  *
3393
+ * All routes are authenticated with the server's auth configuration. Browsers
3394
+ * cannot attach an `Authorization` header to a WebSocket upgrade, so
3395
+ * session-cookie providers authenticate from the upgrade request's cookies.
3396
+ * Clients that authenticate with a header token instead request a short-lived,
3397
+ * single-use ticket from `POST ${apiPrefix}/agents/:agentId/browser/stream-ticket`
3398
+ * and pass it to the upgrade as `?ticket=`, which keeps the reusable credential
3399
+ * out of the URL. Non-browser clients can pass the token as the `apiKey` query
3400
+ * parameter — use a short-lived token, since a URL can be retained in proxy and
3401
+ * access logs, and prefer the `Authorization` header on the HTTP routes where the
3402
+ * client can set one.
3403
+ *
3208
3404
  * **Note**: Requires `ws` package to be installed. If not available, returns null
3209
3405
  * and logs a warning. Browser streaming will be disabled but everything else works.
3210
3406
  *
@@ -3220,6 +3416,7 @@ function propagateClientDisconnect(fetchResponse, res) {
3220
3416
  *
3221
3417
  * const app = new Hono();
3222
3418
  * const browserStream = await setupBrowserStream(app, {
3419
+ * mastra,
3223
3420
  * getToolset: (agentId) => browserToolsets.get(agentId),
3224
3421
  * });
3225
3422
  *
@@ -3242,7 +3439,24 @@ async function setupBrowserStream(app, config) {
3242
3439
  const registry = new ViewerRegistry();
3243
3440
  const rawPrefix = config.apiPrefix ?? "/api";
3244
3441
  const apiPrefix = (rawPrefix.endsWith("/") ? rawPrefix.slice(0, -1) : rawPrefix) || "/api";
3245
- app.get("/browser/:agentId/stream", upgradeWebSocket((c) => {
3442
+ const authenticate = createAuthMiddleware({ mastra: config.mastra });
3443
+ const upgradeTickets = new UpgradeTicketStore();
3444
+ const authorizeUpgrade = async (c, next) => {
3445
+ const ticket = c.req.query("ticket");
3446
+ if (!ticket) return authenticate(c, next);
3447
+ const agentId = c.req.param("agentId");
3448
+ if (!upgradeTickets.consume(ticket, agentId, c.req.query("threadId"))) return c.json({ error: "Invalid or expired upgrade ticket" }, 401);
3449
+ return next();
3450
+ };
3451
+ const allowedOrigins = config.allowedOrigins;
3452
+ const checkOrigin = async (c, next) => {
3453
+ const origin = c.req.header("origin");
3454
+ if (!allowedOrigins || !origin) return next();
3455
+ if (isSameOrigin(origin, c.req.header("x-forwarded-host") ?? c.req.header("host"))) return next();
3456
+ if (!await isOriginAllowed(allowedOrigins, origin, c)) return c.text("Forbidden", 403);
3457
+ return next();
3458
+ };
3459
+ app.get("/browser/:agentId/stream", checkOrigin, authorizeUpgrade, upgradeWebSocket((c) => {
3246
3460
  const agentId = c.req.param("agentId");
3247
3461
  const threadId = c.req.query("threadId");
3248
3462
  const viewerKey = threadId ? `${agentId}:${threadId}` : agentId;
@@ -3264,7 +3478,29 @@ async function setupBrowserStream(app, config) {
3264
3478
  }
3265
3479
  };
3266
3480
  }));
3267
- app.get(`${apiPrefix}/agents/:agentId/browser/session`, async (c) => {
3481
+ const ticketBodyLimit = bodyLimit({
3482
+ maxSize: TICKET_BODY_MAX_BYTES,
3483
+ onError: (c) => c.json({ error: "Request body too large" }, 413)
3484
+ });
3485
+ app.post(`${apiPrefix}/agents/:agentId/browser/stream-ticket`, authenticate, ticketBodyLimit, async (c) => {
3486
+ const agentId = c.req.param("agentId");
3487
+ if (!agentId) return c.json({ error: "Agent ID is required" }, 400);
3488
+ let threadId = c.req.query("threadId");
3489
+ try {
3490
+ const body = await c.req.json();
3491
+ if (!threadId && typeof body?.threadId === "string") threadId = body.threadId;
3492
+ } catch {}
3493
+ const issued = upgradeTickets.issue(agentId, threadId);
3494
+ if (!issued.ok) {
3495
+ const error = issued.reason === "capacity" ? "Too many outstanding browser stream upgrade tickets" : "Too many browser stream upgrade tickets requested";
3496
+ return c.json({ error }, 429);
3497
+ }
3498
+ return c.json({
3499
+ ticket: issued.ticket,
3500
+ expiresIn: issued.expiresIn
3501
+ });
3502
+ });
3503
+ app.get(`${apiPrefix}/agents/:agentId/browser/session`, authenticate, async (c) => {
3268
3504
  const agentId = c.req.param("agentId");
3269
3505
  if (!agentId) return c.json({ error: "Agent ID is required" }, 400);
3270
3506
  const threadId = c.req.query("threadId");
@@ -3279,7 +3515,7 @@ async function setupBrowserStream(app, config) {
3279
3515
  screencastAvailable: true
3280
3516
  });
3281
3517
  });
3282
- app.post(`${apiPrefix}/agents/:agentId/browser/close`, async (c) => {
3518
+ app.post(`${apiPrefix}/agents/:agentId/browser/close`, authenticate, ticketBodyLimit, async (c) => {
3283
3519
  const agentId = c.req.param("agentId");
3284
3520
  if (!agentId) return c.json({ error: "Agent ID is required" }, 400);
3285
3521
  const toolset = await config.getToolset(agentId);
@@ -4696,7 +4932,10 @@ async function createHonoServer(mastra, options = { tools: {} }) {
4696
4932
  honoServerAdapter.registerContextMiddleware();
4697
4933
  const serverMiddleware = mastra.getServerMiddleware?.();
4698
4934
  if (serverMiddleware && serverMiddleware.length > 0) for (const m of serverMiddleware) app.use(m.path, skipIfFrameworkPublic(m.handler));
4935
+ const browserStreamOrigins = server?.cors && typeof server.cors === "object" && "origin" in server.cors ? server.cors.origin : void 0;
4699
4936
  const browserStreamSetup = options.browserStream === false ? null : await setupBrowserStream(app, {
4937
+ mastra,
4938
+ allowedOrigins: browserStreamOrigins,
4700
4939
  getToolset: async (agentId) => {
4701
4940
  const resolveBrowser = async (agent) => {
4702
4941
  if (!agent) return void 0;