@agent-native/core 0.26.9 → 0.28.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.
Files changed (225) hide show
  1. package/dist/agent/run-ownership.d.ts +12 -0
  2. package/dist/agent/run-ownership.d.ts.map +1 -0
  3. package/dist/agent/run-ownership.js +39 -0
  4. package/dist/agent/run-ownership.js.map +1 -0
  5. package/dist/client/AgentPanel.d.ts.map +1 -1
  6. package/dist/client/AgentPanel.js +1 -0
  7. package/dist/client/AgentPanel.js.map +1 -1
  8. package/dist/client/AssistantChat.d.ts.map +1 -1
  9. package/dist/client/AssistantChat.js +9 -6
  10. package/dist/client/AssistantChat.js.map +1 -1
  11. package/dist/client/db-admin/DataGrid.d.ts +42 -0
  12. package/dist/client/db-admin/DataGrid.d.ts.map +1 -0
  13. package/dist/client/db-admin/DataGrid.js +204 -0
  14. package/dist/client/db-admin/DataGrid.js.map +1 -0
  15. package/dist/client/db-admin/DbAdminPage.d.ts +2 -0
  16. package/dist/client/db-admin/DbAdminPage.d.ts.map +1 -0
  17. package/dist/client/db-admin/DbAdminPage.js +72 -0
  18. package/dist/client/db-admin/DbAdminPage.js.map +1 -0
  19. package/dist/client/db-admin/DevDatabaseLink.d.ts +19 -0
  20. package/dist/client/db-admin/DevDatabaseLink.d.ts.map +1 -0
  21. package/dist/client/db-admin/DevDatabaseLink.js +25 -0
  22. package/dist/client/db-admin/DevDatabaseLink.js.map +1 -0
  23. package/dist/client/db-admin/EditableCell.d.ts +26 -0
  24. package/dist/client/db-admin/EditableCell.d.ts.map +1 -0
  25. package/dist/client/db-admin/EditableCell.js +150 -0
  26. package/dist/client/db-admin/EditableCell.js.map +1 -0
  27. package/dist/client/db-admin/FilterBar.d.ts +8 -0
  28. package/dist/client/db-admin/FilterBar.d.ts.map +1 -0
  29. package/dist/client/db-admin/FilterBar.js +68 -0
  30. package/dist/client/db-admin/FilterBar.js.map +1 -0
  31. package/dist/client/db-admin/ResultsGrid.d.ts +6 -0
  32. package/dist/client/db-admin/ResultsGrid.d.ts.map +1 -0
  33. package/dist/client/db-admin/ResultsGrid.js +41 -0
  34. package/dist/client/db-admin/ResultsGrid.js.map +1 -0
  35. package/dist/client/db-admin/RowSidePanel.d.ts +18 -0
  36. package/dist/client/db-admin/RowSidePanel.d.ts.map +1 -0
  37. package/dist/client/db-admin/RowSidePanel.js +104 -0
  38. package/dist/client/db-admin/RowSidePanel.js.map +1 -0
  39. package/dist/client/db-admin/SqlEditor.d.ts +8 -0
  40. package/dist/client/db-admin/SqlEditor.d.ts.map +1 -0
  41. package/dist/client/db-admin/SqlEditor.js +350 -0
  42. package/dist/client/db-admin/SqlEditor.js.map +1 -0
  43. package/dist/client/db-admin/TableBrowser.d.ts +10 -0
  44. package/dist/client/db-admin/TableBrowser.d.ts.map +1 -0
  45. package/dist/client/db-admin/TableBrowser.js +61 -0
  46. package/dist/client/db-admin/TableBrowser.js.map +1 -0
  47. package/dist/client/db-admin/TableEditor.d.ts +9 -0
  48. package/dist/client/db-admin/TableEditor.d.ts.map +1 -0
  49. package/dist/client/db-admin/TableEditor.js +254 -0
  50. package/dist/client/db-admin/TableEditor.js.map +1 -0
  51. package/dist/client/db-admin/cell-format.d.ts +55 -0
  52. package/dist/client/db-admin/cell-format.d.ts.map +1 -0
  53. package/dist/client/db-admin/cell-format.js +223 -0
  54. package/dist/client/db-admin/cell-format.js.map +1 -0
  55. package/dist/client/db-admin/changeset.d.ts +74 -0
  56. package/dist/client/db-admin/changeset.d.ts.map +1 -0
  57. package/dist/client/db-admin/changeset.js +169 -0
  58. package/dist/client/db-admin/changeset.js.map +1 -0
  59. package/dist/client/db-admin/export-utils.d.ts +15 -0
  60. package/dist/client/db-admin/export-utils.d.ts.map +1 -0
  61. package/dist/client/db-admin/export-utils.js +62 -0
  62. package/dist/client/db-admin/export-utils.js.map +1 -0
  63. package/dist/client/db-admin/index.d.ts +7 -0
  64. package/dist/client/db-admin/index.d.ts.map +1 -0
  65. package/dist/client/db-admin/index.js +8 -0
  66. package/dist/client/db-admin/index.js.map +1 -0
  67. package/dist/client/db-admin/sql-storage.d.ts +35 -0
  68. package/dist/client/db-admin/sql-storage.d.ts.map +1 -0
  69. package/dist/client/db-admin/sql-storage.js +117 -0
  70. package/dist/client/db-admin/sql-storage.js.map +1 -0
  71. package/dist/client/db-admin/storage.d.ts +24 -0
  72. package/dist/client/db-admin/storage.d.ts.map +1 -0
  73. package/dist/client/db-admin/storage.js +50 -0
  74. package/dist/client/db-admin/storage.js.map +1 -0
  75. package/dist/client/db-admin/useAgentSync.d.ts +22 -0
  76. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -0
  77. package/dist/client/db-admin/useAgentSync.js +120 -0
  78. package/dist/client/db-admin/useAgentSync.js.map +1 -0
  79. package/dist/client/db-admin/useDbAdmin.d.ts +20 -0
  80. package/dist/client/db-admin/useDbAdmin.d.ts.map +1 -0
  81. package/dist/client/db-admin/useDbAdmin.js +154 -0
  82. package/dist/client/db-admin/useDbAdmin.js.map +1 -0
  83. package/dist/client/index.d.ts +2 -1
  84. package/dist/client/index.d.ts.map +1 -1
  85. package/dist/client/index.js +2 -1
  86. package/dist/client/index.js.map +1 -1
  87. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  88. package/dist/client/settings/SettingsPanel.js +8 -4
  89. package/dist/client/settings/SettingsPanel.js.map +1 -1
  90. package/dist/client/use-dev-mode.d.ts +20 -2
  91. package/dist/client/use-dev-mode.d.ts.map +1 -1
  92. package/dist/client/use-dev-mode.js +49 -14
  93. package/dist/client/use-dev-mode.js.map +1 -1
  94. package/dist/credentials/index.d.ts.map +1 -1
  95. package/dist/credentials/index.js +25 -5
  96. package/dist/credentials/index.js.map +1 -1
  97. package/dist/db-admin/agent-tools.d.ts +15 -0
  98. package/dist/db-admin/agent-tools.d.ts.map +1 -0
  99. package/dist/db-admin/agent-tools.js +147 -0
  100. package/dist/db-admin/agent-tools.js.map +1 -0
  101. package/dist/db-admin/operations.d.ts +17 -0
  102. package/dist/db-admin/operations.d.ts.map +1 -0
  103. package/dist/db-admin/operations.js +541 -0
  104. package/dist/db-admin/operations.js.map +1 -0
  105. package/dist/db-admin/routes.d.ts +5 -0
  106. package/dist/db-admin/routes.d.ts.map +1 -0
  107. package/dist/db-admin/routes.js +134 -0
  108. package/dist/db-admin/routes.js.map +1 -0
  109. package/dist/db-admin/types.d.ts +85 -0
  110. package/dist/db-admin/types.d.ts.map +1 -0
  111. package/dist/db-admin/types.js +9 -0
  112. package/dist/db-admin/types.js.map +1 -0
  113. package/dist/extensions/url-safety.d.ts +20 -0
  114. package/dist/extensions/url-safety.d.ts.map +1 -1
  115. package/dist/extensions/url-safety.js +43 -0
  116. package/dist/extensions/url-safety.js.map +1 -1
  117. package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
  118. package/dist/file-upload/actions/upload-image.js +6 -1
  119. package/dist/file-upload/actions/upload-image.js.map +1 -1
  120. package/dist/integrations/adapters/email.d.ts.map +1 -1
  121. package/dist/integrations/adapters/email.js +112 -0
  122. package/dist/integrations/adapters/email.js.map +1 -1
  123. package/dist/integrations/types.d.ts +11 -0
  124. package/dist/integrations/types.d.ts.map +1 -1
  125. package/dist/integrations/types.js.map +1 -1
  126. package/dist/scripts/db/exec.d.ts.map +1 -1
  127. package/dist/scripts/db/exec.js +2 -1
  128. package/dist/scripts/db/exec.js.map +1 -1
  129. package/dist/scripts/db/index.d.ts.map +1 -1
  130. package/dist/scripts/db/index.js +1 -0
  131. package/dist/scripts/db/index.js.map +1 -1
  132. package/dist/scripts/db/migrate-encrypt-credentials.d.ts +28 -0
  133. package/dist/scripts/db/migrate-encrypt-credentials.d.ts.map +1 -0
  134. package/dist/scripts/db/migrate-encrypt-credentials.js +190 -0
  135. package/dist/scripts/db/migrate-encrypt-credentials.js.map +1 -0
  136. package/dist/scripts/db/query.d.ts.map +1 -1
  137. package/dist/scripts/db/query.js +2 -1
  138. package/dist/scripts/db/query.js.map +1 -1
  139. package/dist/scripts/db/safety.d.ts +1 -0
  140. package/dist/scripts/db/safety.d.ts.map +1 -1
  141. package/dist/scripts/db/safety.js +32 -0
  142. package/dist/scripts/db/safety.js.map +1 -1
  143. package/dist/scripts/db/scoping.d.ts.map +1 -1
  144. package/dist/scripts/db/scoping.js +11 -1
  145. package/dist/scripts/db/scoping.js.map +1 -1
  146. package/dist/secrets/crypto.d.ts +28 -0
  147. package/dist/secrets/crypto.d.ts.map +1 -0
  148. package/dist/secrets/crypto.js +81 -0
  149. package/dist/secrets/crypto.js.map +1 -0
  150. package/dist/secrets/storage.d.ts.map +1 -1
  151. package/dist/secrets/storage.js +3 -61
  152. package/dist/secrets/storage.js.map +1 -1
  153. package/dist/server/action-discovery.d.ts.map +1 -1
  154. package/dist/server/action-discovery.js +5 -2
  155. package/dist/server/action-discovery.js.map +1 -1
  156. package/dist/server/action-routes.d.ts.map +1 -1
  157. package/dist/server/action-routes.js +24 -7
  158. package/dist/server/action-routes.js.map +1 -1
  159. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  160. package/dist/server/agent-chat-plugin.js +49 -2
  161. package/dist/server/agent-chat-plugin.js.map +1 -1
  162. package/dist/server/auth.d.ts +1 -1
  163. package/dist/server/auth.d.ts.map +1 -1
  164. package/dist/server/auth.js.map +1 -1
  165. package/dist/server/better-auth-instance.js +3 -3
  166. package/dist/server/better-auth-instance.js.map +1 -1
  167. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  168. package/dist/server/core-routes-plugin.js +5 -0
  169. package/dist/server/core-routes-plugin.js.map +1 -1
  170. package/dist/server/csrf.d.ts.map +1 -1
  171. package/dist/server/csrf.js +9 -1
  172. package/dist/server/csrf.js.map +1 -1
  173. package/dist/server/design-token-utils.d.ts +8 -1
  174. package/dist/server/design-token-utils.d.ts.map +1 -1
  175. package/dist/server/design-token-utils.js +12 -4
  176. package/dist/server/design-token-utils.js.map +1 -1
  177. package/dist/templates/default/AGENTS.md +4 -4
  178. package/dist/templates/default/app/routes/database.tsx +13 -0
  179. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  180. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
  181. package/dist/vite/client.d.ts.map +1 -1
  182. package/dist/vite/client.js +4 -0
  183. package/dist/vite/client.js.map +1 -1
  184. package/docs/content/a2a-protocol.md +2 -2
  185. package/docs/content/actions.md +2 -54
  186. package/docs/content/agent-mentions.md +1 -1
  187. package/docs/content/agent-teams.md +1 -1
  188. package/docs/content/authentication.md +2 -2
  189. package/docs/content/cli-adapters.md +33 -17
  190. package/docs/content/client.md +11 -20
  191. package/docs/content/code-agents-ui.md +19 -6
  192. package/docs/content/context-awareness.md +36 -20
  193. package/docs/content/database.md +3 -3
  194. package/docs/content/deployment.md +8 -8
  195. package/docs/content/dispatch.md +1 -1
  196. package/docs/content/external-agents.md +5 -1
  197. package/docs/content/faq.md +1 -0
  198. package/docs/content/frames.md +116 -30
  199. package/docs/content/getting-started.md +15 -14
  200. package/docs/content/mcp-clients.md +1 -1
  201. package/docs/content/mcp-protocol.md +11 -88
  202. package/docs/content/messaging.md +1 -1
  203. package/docs/content/migration-workbench.md +13 -87
  204. package/docs/content/multi-app-workspace.md +2 -38
  205. package/docs/content/multi-tenancy.md +3 -26
  206. package/docs/content/onboarding.md +10 -3
  207. package/docs/content/recurring-jobs.md +2 -2
  208. package/docs/content/security.md +33 -1
  209. package/docs/content/server.md +1 -1
  210. package/docs/content/template-assets.md +9 -9
  211. package/docs/content/template-brain.md +114 -388
  212. package/docs/content/template-clips.md +42 -2
  213. package/docs/content/template-content.md +1 -1
  214. package/docs/content/template-design.md +27 -0
  215. package/docs/content/template-dispatch.md +3 -3
  216. package/docs/content/template-forms.md +6 -6
  217. package/docs/content/template-starter.md +2 -2
  218. package/docs/content/using-your-agent.md +56 -0
  219. package/docs/content/workspace-management.md +6 -6
  220. package/docs/content/workspace.md +28 -9
  221. package/package.json +10 -3
  222. package/src/templates/default/AGENTS.md +4 -4
  223. package/src/templates/default/app/routes/database.tsx +13 -0
  224. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +9 -2
  225. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +7 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * HTTP routes for the dev-mode database admin.
3
+ *
4
+ * Mounted under `/_agent-native/db-admin/*`. EVERY handler self-gates on
5
+ * `NODE_ENV === "development"` (this is the authoritative gate — see
6
+ * `isDevEnvironment`). Real logins work locally; there is no localhost or
7
+ * `AUTH_MODE=local` shim. The DB admin exposes raw, unscoped full-database
8
+ * access and must NEVER be reachable in a deployed / production-mode app.
9
+ */
10
+ import { defineEventHandler, getMethod, setResponseHeader, setResponseStatus, } from "h3";
11
+ import { getH3App } from "../server/framework-request-handler.js";
12
+ import { readBody } from "../server/h3-helpers.js";
13
+ import { applyMutations, getRows, getTableSchema, listTables, runSql, DbAdminConfirmRequiredError, } from "./operations.js";
14
+ /** Authoritative gate for the DB admin: development mode only.
15
+ * Available purely on `NODE_ENV === "development"` — real logins work locally
16
+ * and there is no localhost / `AUTH_MODE=local` shim. The normal
17
+ * `/_agent-native/*` auth layer still requires a signed-in user on top of this. */
18
+ function isDevEnvironment() {
19
+ return process.env.NODE_ENV === "development";
20
+ }
21
+ function decodeSegment(value) {
22
+ if (!value)
23
+ return undefined;
24
+ try {
25
+ return decodeURIComponent(value);
26
+ }
27
+ catch {
28
+ return value;
29
+ }
30
+ }
31
+ export function mountDbAdminRoutes(nitroApp, options = {}) {
32
+ const routePrefix = options.routePrefix ?? "/_agent-native";
33
+ const basePath = `${routePrefix}/db-admin`;
34
+ getH3App(nitroApp).use(basePath, defineEventHandler(async (event) => {
35
+ setResponseHeader(event, "Cache-Control", "no-store");
36
+ // Authoritative gate: development mode only (NODE_ENV === "development").
37
+ if (!isDevEnvironment()) {
38
+ setResponseStatus(event, 403);
39
+ return {
40
+ ok: false,
41
+ error: "DB admin is only available in development mode.",
42
+ };
43
+ }
44
+ const method = getMethod(event);
45
+ // event.path is relative to the mount base path under h3's .use().
46
+ const raw = (event.path || "/").split("?")[0];
47
+ const segments = raw
48
+ .replace(/^\/+/, "")
49
+ .split("/")
50
+ .filter(Boolean)
51
+ .map(decodeSegment);
52
+ try {
53
+ // GET /overview
54
+ if (segments[0] === "overview" && segments.length === 1) {
55
+ if (method !== "GET")
56
+ return methodNotAllowed(event);
57
+ const result = await listTables();
58
+ return { ok: true, ...result };
59
+ }
60
+ // /table/:name/...
61
+ if (segments[0] === "table") {
62
+ const name = segments[1];
63
+ if (!name)
64
+ return badRequest(event, "Table name is required");
65
+ const sub = segments[2];
66
+ if (sub === "schema" && segments.length === 3) {
67
+ if (method !== "GET")
68
+ return methodNotAllowed(event);
69
+ const table = await getTableSchema(name);
70
+ return { ok: true, table };
71
+ }
72
+ if (sub === "rows" && segments.length === 3) {
73
+ if (method !== "POST")
74
+ return methodNotAllowed(event);
75
+ const body = await readBody(event);
76
+ const result = await getRows(name, {
77
+ page: Number(body.page) || 1,
78
+ pageSize: Number(body.pageSize) || 50,
79
+ sort: body.sort,
80
+ filters: body.filters,
81
+ });
82
+ return { ok: true, ...result };
83
+ }
84
+ if (sub === "mutate" && segments.length === 3) {
85
+ if (method !== "POST")
86
+ return methodNotAllowed(event);
87
+ const body = await readBody(event);
88
+ const result = await applyMutations(name, body ?? {});
89
+ return { ok: true, ...result };
90
+ }
91
+ return notFound(event, "Unknown db-admin table route");
92
+ }
93
+ // POST /query
94
+ if (segments[0] === "query" && segments.length === 1) {
95
+ if (method !== "POST")
96
+ return methodNotAllowed(event);
97
+ const body = await readBody(event);
98
+ try {
99
+ const result = await runSql(String(body.sql ?? ""), Array.isArray(body.params) ? body.params : undefined, { confirmDestructive: body.confirmDestructive === true });
100
+ return { ok: true, ...result };
101
+ }
102
+ catch (err) {
103
+ if (err instanceof DbAdminConfirmRequiredError) {
104
+ setResponseStatus(event, 400);
105
+ return {
106
+ ok: false,
107
+ error: err.message,
108
+ needsConfirm: true,
109
+ };
110
+ }
111
+ throw err;
112
+ }
113
+ }
114
+ return notFound(event, "Unknown db-admin route");
115
+ }
116
+ catch (err) {
117
+ setResponseStatus(event, 500);
118
+ return { ok: false, error: String(err) };
119
+ }
120
+ }));
121
+ }
122
+ function methodNotAllowed(event) {
123
+ setResponseStatus(event, 405);
124
+ return { ok: false, error: "Method not allowed" };
125
+ }
126
+ function badRequest(event, error) {
127
+ setResponseStatus(event, 400);
128
+ return { ok: false, error };
129
+ }
130
+ function notFound(event, error) {
131
+ setResponseStatus(event, 404);
132
+ return { ok: false, error };
133
+ }
134
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/db-admin/routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HTTP routes for the dev-mode database admin.\n *\n * Mounted under `/_agent-native/db-admin/*`. EVERY handler self-gates on\n * `NODE_ENV === \"development\"` (this is the authoritative gate — see\n * `isDevEnvironment`). Real logins work locally; there is no localhost or\n * `AUTH_MODE=local` shim. The DB admin exposes raw, unscoped full-database\n * access and must NEVER be reachable in a deployed / production-mode app.\n */\nimport {\n defineEventHandler,\n getMethod,\n setResponseHeader,\n setResponseStatus,\n} from \"h3\";\nimport type { H3Event } from \"h3\";\nimport { getH3App } from \"../server/framework-request-handler.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n applyMutations,\n getRows,\n getTableSchema,\n listTables,\n runSql,\n DbAdminConfirmRequiredError,\n} from \"./operations.js\";\nimport type { DbAdminMutation, DbAdminRowsRequest } from \"./types.js\";\n\nexport interface MountDbAdminRoutesOptions {\n routePrefix?: string;\n}\n\n/** Authoritative gate for the DB admin: development mode only.\n * Available purely on `NODE_ENV === \"development\"` — real logins work locally\n * and there is no localhost / `AUTH_MODE=local` shim. The normal\n * `/_agent-native/*` auth layer still requires a signed-in user on top of this. */\nfunction isDevEnvironment(): boolean {\n return process.env.NODE_ENV === \"development\";\n}\n\nfunction decodeSegment(value: string | undefined): string | undefined {\n if (!value) return undefined;\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\nexport function mountDbAdminRoutes(\n nitroApp: any,\n options: MountDbAdminRoutesOptions = {},\n): void {\n const routePrefix = options.routePrefix ?? \"/_agent-native\";\n const basePath = `${routePrefix}/db-admin`;\n\n getH3App(nitroApp).use(\n basePath,\n defineEventHandler(async (event: H3Event) => {\n setResponseHeader(event, \"Cache-Control\", \"no-store\");\n\n // Authoritative gate: development mode only (NODE_ENV === \"development\").\n if (!isDevEnvironment()) {\n setResponseStatus(event, 403);\n return {\n ok: false,\n error: \"DB admin is only available in development mode.\",\n };\n }\n\n const method = getMethod(event);\n // event.path is relative to the mount base path under h3's .use().\n const raw = (event.path || \"/\").split(\"?\")[0];\n const segments = raw\n .replace(/^\\/+/, \"\")\n .split(\"/\")\n .filter(Boolean)\n .map(decodeSegment);\n\n try {\n // GET /overview\n if (segments[0] === \"overview\" && segments.length === 1) {\n if (method !== \"GET\") return methodNotAllowed(event);\n const result = await listTables();\n return { ok: true, ...result };\n }\n\n // /table/:name/...\n if (segments[0] === \"table\") {\n const name = segments[1];\n if (!name) return badRequest(event, \"Table name is required\");\n const sub = segments[2];\n\n if (sub === \"schema\" && segments.length === 3) {\n if (method !== \"GET\") return methodNotAllowed(event);\n const table = await getTableSchema(name);\n return { ok: true, table };\n }\n\n if (sub === \"rows\" && segments.length === 3) {\n if (method !== \"POST\") return methodNotAllowed(event);\n const body = await readBody<Partial<DbAdminRowsRequest>>(event);\n const result = await getRows(name, {\n page: Number(body.page) || 1,\n pageSize: Number(body.pageSize) || 50,\n sort: body.sort,\n filters: body.filters,\n });\n return { ok: true, ...result };\n }\n\n if (sub === \"mutate\" && segments.length === 3) {\n if (method !== \"POST\") return methodNotAllowed(event);\n const body = await readBody<DbAdminMutation>(event);\n const result = await applyMutations(name, body ?? {});\n return { ok: true, ...result };\n }\n\n return notFound(event, \"Unknown db-admin table route\");\n }\n\n // POST /query\n if (segments[0] === \"query\" && segments.length === 1) {\n if (method !== \"POST\") return methodNotAllowed(event);\n const body = await readBody<{\n sql?: string;\n params?: unknown[];\n confirmDestructive?: boolean;\n }>(event);\n try {\n const result = await runSql(\n String(body.sql ?? \"\"),\n Array.isArray(body.params) ? body.params : undefined,\n { confirmDestructive: body.confirmDestructive === true },\n );\n return { ok: true, ...result };\n } catch (err) {\n if (err instanceof DbAdminConfirmRequiredError) {\n setResponseStatus(event, 400);\n return {\n ok: false,\n error: err.message,\n needsConfirm: true,\n };\n }\n throw err;\n }\n }\n\n return notFound(event, \"Unknown db-admin route\");\n } catch (err) {\n setResponseStatus(event, 500);\n return { ok: false, error: String(err) };\n }\n }),\n );\n}\n\nfunction methodNotAllowed(event: H3Event) {\n setResponseStatus(event, 405);\n return { ok: false, error: \"Method not allowed\" };\n}\n\nfunction badRequest(event: H3Event, error: string) {\n setResponseStatus(event, 400);\n return { ok: false, error };\n}\n\nfunction notFound(event: H3Event, error: string) {\n setResponseStatus(event, 404);\n return { ok: false, error };\n}\n"]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared contract types for the dev-mode database admin (Supabase-Studio-like).
3
+ *
4
+ * The backend (operations / routes / agent tools) and the frontend both import
5
+ * these so request/response shapes stay in lockstep. Keep this file free of any
6
+ * runtime imports — it is a pure type module.
7
+ */
8
+ export type DbAdminDialect = "sqlite" | "postgres" | "d1";
9
+ export interface DbAdminColumn {
10
+ name: string;
11
+ type: string;
12
+ nullable: boolean;
13
+ pk: boolean;
14
+ defaultValue: string | null;
15
+ autoIncrement?: boolean;
16
+ }
17
+ export interface DbAdminForeignKey {
18
+ column: string;
19
+ refTable: string;
20
+ refColumn: string;
21
+ }
22
+ export interface DbAdminIndex {
23
+ name: string;
24
+ unique: boolean;
25
+ columns: string[];
26
+ }
27
+ export interface DbAdminTableSummary {
28
+ name: string;
29
+ type: "table" | "view";
30
+ rowCount: number | null;
31
+ }
32
+ export interface DbAdminTableSchema {
33
+ name: string;
34
+ type: "table" | "view";
35
+ columns: DbAdminColumn[];
36
+ primaryKey: string[];
37
+ foreignKeys: DbAdminForeignKey[];
38
+ indexes: DbAdminIndex[];
39
+ rowCount: number | null;
40
+ }
41
+ export type DbAdminFilterOp = "eq" | "neq" | "lt" | "lte" | "gt" | "gte" | "like" | "ilike" | "in" | "is_null" | "not_null";
42
+ export interface DbAdminFilter {
43
+ column: string;
44
+ op: DbAdminFilterOp;
45
+ value?: unknown;
46
+ }
47
+ export interface DbAdminSort {
48
+ column: string;
49
+ dir: "asc" | "desc";
50
+ }
51
+ export interface DbAdminRowsRequest {
52
+ page: number;
53
+ pageSize: number;
54
+ sort?: DbAdminSort[];
55
+ filters?: DbAdminFilter[];
56
+ }
57
+ export interface DbAdminRowsResult {
58
+ columns: DbAdminColumn[];
59
+ rows: Record<string, unknown>[];
60
+ total: number;
61
+ page: number;
62
+ pageSize: number;
63
+ }
64
+ export interface DbAdminMutation {
65
+ inserts?: Record<string, unknown>[];
66
+ updates?: {
67
+ where: Record<string, unknown>;
68
+ set: Record<string, unknown>;
69
+ }[];
70
+ deletes?: Record<string, unknown>[];
71
+ dryRun?: boolean;
72
+ }
73
+ export interface DbAdminMutationResult {
74
+ sql: string[];
75
+ inserted: number;
76
+ updated: number;
77
+ deleted: number;
78
+ }
79
+ export interface DbAdminQueryResult {
80
+ columns: string[];
81
+ rows: Record<string, unknown>[];
82
+ rowsAffected: number;
83
+ durationMs: number;
84
+ }
85
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/db-admin/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared contract types for the dev-mode database admin (Supabase-Studio-like).
3
+ *
4
+ * The backend (operations / routes / agent tools) and the frontend both import
5
+ * these so request/response shapes stay in lockstep. Keep this file free of any
6
+ * runtime imports — it is a pure type module.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/db-admin/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG","sourcesContent":["/**\n * Shared contract types for the dev-mode database admin (Supabase-Studio-like).\n *\n * The backend (operations / routes / agent tools) and the frontend both import\n * these so request/response shapes stay in lockstep. Keep this file free of any\n * runtime imports — it is a pure type module.\n */\n\nexport type DbAdminDialect = \"sqlite\" | \"postgres\" | \"d1\";\n\nexport interface DbAdminColumn {\n name: string;\n type: string;\n nullable: boolean;\n pk: boolean;\n defaultValue: string | null;\n autoIncrement?: boolean;\n}\n\nexport interface DbAdminForeignKey {\n column: string;\n refTable: string;\n refColumn: string;\n}\n\nexport interface DbAdminIndex {\n name: string;\n unique: boolean;\n columns: string[];\n}\n\nexport interface DbAdminTableSummary {\n name: string;\n type: \"table\" | \"view\";\n rowCount: number | null;\n}\n\nexport interface DbAdminTableSchema {\n name: string;\n type: \"table\" | \"view\";\n columns: DbAdminColumn[];\n primaryKey: string[];\n foreignKeys: DbAdminForeignKey[];\n indexes: DbAdminIndex[];\n rowCount: number | null;\n}\n\nexport type DbAdminFilterOp =\n | \"eq\"\n | \"neq\"\n | \"lt\"\n | \"lte\"\n | \"gt\"\n | \"gte\"\n | \"like\"\n | \"ilike\"\n | \"in\"\n | \"is_null\"\n | \"not_null\";\n\nexport interface DbAdminFilter {\n column: string;\n op: DbAdminFilterOp;\n value?: unknown;\n}\n\nexport interface DbAdminSort {\n column: string;\n dir: \"asc\" | \"desc\";\n}\n\nexport interface DbAdminRowsRequest {\n page: number;\n pageSize: number;\n sort?: DbAdminSort[];\n filters?: DbAdminFilter[];\n}\n\nexport interface DbAdminRowsResult {\n columns: DbAdminColumn[];\n rows: Record<string, unknown>[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface DbAdminMutation {\n inserts?: Record<string, unknown>[];\n updates?: { where: Record<string, unknown>; set: Record<string, unknown> }[];\n deletes?: Record<string, unknown>[];\n dryRun?: boolean;\n}\n\nexport interface DbAdminMutationResult {\n sql: string[];\n inserted: number;\n updated: number;\n deleted: number;\n}\n\nexport interface DbAdminQueryResult {\n columns: string[];\n rows: Record<string, unknown>[];\n rowsAffected: number;\n durationMs: number;\n}\n"]}
@@ -21,6 +21,26 @@ export declare function isBlockedExtensionUrlWithDns(url: string): Promise<boole
21
21
  * `isBlockedExtensionUrlWithDns` will still have caught most rebinding cases.
22
22
  */
23
23
  export declare function createSsrfSafeDispatcher(): Promise<unknown | null>;
24
+ /**
25
+ * SSRF-safe `fetch` for any server-side request to a user/agent-supplied URL.
26
+ *
27
+ * Applies the same protections the extension proxy uses, so every call site
28
+ * that fetches an untrusted URL gets them without re-implementing the loop:
29
+ * 1. Pre-flight DNS-aware private-address check (isBlockedExtensionUrlWithDns)
30
+ * on the initial URL and on every redirect hop.
31
+ * 2. A connect-time dispatcher that re-checks the resolved IP at TCP-connect
32
+ * time (closes the DNS-rebinding TOCTOU) when undici is available.
33
+ * 3. Manual redirect handling — a public URL cannot 30x-redirect into the
34
+ * private network because each hop is re-validated before it is followed.
35
+ *
36
+ * Throws an Error whose message starts with "SSRF blocked:" when a target
37
+ * (initial or via redirect) resolves to a private/internal address, or when the
38
+ * redirect limit is exceeded. Otherwise returns the final Response.
39
+ */
40
+ export declare function ssrfSafeFetch(url: string, init?: RequestInit, options?: {
41
+ maxRedirects?: number;
42
+ }): Promise<Response>;
24
43
  export { isBlockedExtensionUrl as isBlockedToolUrl };
25
44
  export { isBlockedExtensionUrlWithDns as isBlockedToolUrlWithDns };
45
+ export { ssrfSafeFetch as ssrfSafeToolFetch };
26
46
  //# sourceMappingURL=url-safety.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-safety.d.ts","sourceRoot":"","sources":["../../src/extensions/url-safety.ts"],"names":[],"mappings":"AA4FA,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAoB1D;AASD;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAmExE;AAQD,OAAO,EAAE,qBAAqB,IAAI,gBAAgB,EAAE,CAAC;AACrD,OAAO,EAAE,4BAA4B,IAAI,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"url-safety.d.ts","sourceRoot":"","sources":["../../src/extensions/url-safety.ts"],"names":[],"mappings":"AA4FA,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAoB1D;AASD;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAmExE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,OAAO,CAAC,QAAQ,CAAC,CA6BnB;AAQD,OAAO,EAAE,qBAAqB,IAAI,gBAAgB,EAAE,CAAC;AACrD,OAAO,EAAE,4BAA4B,IAAI,uBAAuB,EAAE,CAAC;AACnE,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,CAAC"}
@@ -222,6 +222,48 @@ export async function createSsrfSafeDispatcher() {
222
222
  },
223
223
  });
224
224
  }
225
+ /**
226
+ * SSRF-safe `fetch` for any server-side request to a user/agent-supplied URL.
227
+ *
228
+ * Applies the same protections the extension proxy uses, so every call site
229
+ * that fetches an untrusted URL gets them without re-implementing the loop:
230
+ * 1. Pre-flight DNS-aware private-address check (isBlockedExtensionUrlWithDns)
231
+ * on the initial URL and on every redirect hop.
232
+ * 2. A connect-time dispatcher that re-checks the resolved IP at TCP-connect
233
+ * time (closes the DNS-rebinding TOCTOU) when undici is available.
234
+ * 3. Manual redirect handling — a public URL cannot 30x-redirect into the
235
+ * private network because each hop is re-validated before it is followed.
236
+ *
237
+ * Throws an Error whose message starts with "SSRF blocked:" when a target
238
+ * (initial or via redirect) resolves to a private/internal address, or when the
239
+ * redirect limit is exceeded. Otherwise returns the final Response.
240
+ */
241
+ export async function ssrfSafeFetch(url, init = {}, options = {}) {
242
+ const maxRedirects = options.maxRedirects ?? 3;
243
+ const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;
244
+ let currentUrl = url;
245
+ for (let hop = 0; hop <= maxRedirects; hop++) {
246
+ if (await isBlockedExtensionUrlWithDns(currentUrl)) {
247
+ throw new Error(`SSRF blocked: refusing to fetch private/internal address (${currentUrl})`);
248
+ }
249
+ const fetchOpts = {
250
+ ...init,
251
+ redirect: "manual",
252
+ };
253
+ if (dispatcher)
254
+ fetchOpts.dispatcher = dispatcher;
255
+ const response = await fetch(currentUrl, fetchOpts);
256
+ if (response.status >= 300 && response.status < 400) {
257
+ const location = response.headers.get("location");
258
+ if (!location)
259
+ return response;
260
+ currentUrl = new URL(location, currentUrl).href;
261
+ continue;
262
+ }
263
+ return response;
264
+ }
265
+ throw new Error(`SSRF blocked: too many redirects (>${maxRedirects}) while fetching ${url}`);
266
+ }
225
267
  // ─────────────────────────────────────────────────────────────────────────────
226
268
  // Legacy aliases — predate the Tools → Extensions rename. Templates import
227
269
  // these via the legacy `@agent-native/core/tools/url-safety` subpath; keep
@@ -229,4 +271,5 @@ export async function createSsrfSafeDispatcher() {
229
271
  // ─────────────────────────────────────────────────────────────────────────────
230
272
  export { isBlockedExtensionUrl as isBlockedToolUrl };
231
273
  export { isBlockedExtensionUrlWithDns as isBlockedToolUrlWithDns };
274
+ export { ssrfSafeFetch as ssrfSafeToolFetch };
232
275
  //# sourceMappingURL=url-safety.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-safety.js","sourceRoot":"","sources":["../../src/extensions/url-safety.ts"],"names":[],"mappings":"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","sourcesContent":["const METADATA_HOSTS = [\n \"metadata.google.internal\",\n \"metadata.google.internal.\",\n];\n\nconst DNS_REBIND_SUFFIXES = [\n \".nip.io\",\n \".sslip.io\",\n \".xip.io\",\n \".localtest.me\",\n \".lvh.me\",\n];\n\nfunction isPrivateIpv4(a: number, b: number, c = 0, d = 0): boolean {\n if (![a, b, c, d].every((part) => part >= 0 && part <= 255)) return true;\n if (a === 127) return true;\n if (a === 10) return true;\n if (a === 172 && b >= 16 && b <= 31) return true;\n if (a === 192 && b === 168) return true;\n if (a === 169 && b === 254) return true;\n if (a === 0) return true;\n if (a === 100 && b >= 64 && b <= 127) return true;\n if (a === 192 && b === 0) return true;\n if (a === 198 && (b === 18 || b === 19)) return true;\n if (a === 192 && b === 0 && c === 2) return true;\n if (a === 198 && b === 51 && c === 100) return true;\n if (a === 203 && b === 0 && c === 113) return true;\n if (a >= 224) return true;\n return false;\n}\n\nfunction isPrivateIpv4MappedHex(host: string): boolean {\n const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);\n if (!mapped) return false;\n const high = Number.parseInt(mapped[1], 16);\n const low = Number.parseInt(mapped[2], 16);\n if (high < 0 || high > 0xffff || low < 0 || low > 0xffff) return false;\n const a = (high >> 8) & 0xff;\n const b = high & 0xff;\n const c = (low >> 8) & 0xff;\n const d = low & 0xff;\n return isPrivateIpv4(a, b, c, d);\n}\n\nfunction isPrivateHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (\n host === \"localhost\" ||\n host === \"::1\" ||\n host === \"::0\" ||\n host === \"::\"\n ) {\n return true;\n }\n if (METADATA_HOSTS.includes(host)) return true;\n\n // IPv6 ULA/link-local/multicast.\n if (/^f[cd]/.test(host) || /^fe[89ab]/.test(host)) return true;\n if (/^ff/i.test(host)) return true;\n\n // IPv4-mapped IPv6. URL parsing may preserve dotted form in some runtimes\n // or normalize it to hex, e.g. [::ffff:127.0.0.1] -> ::ffff:7f00:1.\n const v4mappedDotted = host.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (v4mappedDotted) {\n const [a, b, c, d] = v4mappedDotted[1].split(\".\").map(Number);\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n if (isPrivateIpv4MappedHex(host)) return true;\n\n // Dotted IPv4. URL parsing normalizes shorthand/octal/hex IPv4 forms to\n // dotted decimal before we reach this point.\n const parts = host.split(\".\");\n if (parts.length === 4 && parts.every((p) => /^\\d+$/.test(p))) {\n const [a, b, c, d] = parts.map(Number);\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n\n // Decimal integer IPv4.\n if (/^\\d+$/.test(host)) {\n const num = Number(host);\n if (num >= 0 && num <= 0xffffffff) {\n const a = (num >>> 24) & 0xff;\n const b = (num >>> 16) & 0xff;\n const c = (num >>> 8) & 0xff;\n const d = num & 0xff;\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n }\n\n return false;\n}\n\nexport function isBlockedExtensionUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return true;\n }\n const host = parsed.hostname.toLowerCase();\n if (isPrivateHost(host)) return true;\n if (\n DNS_REBIND_SUFFIXES.some((suffix) => {\n const bare = suffix.slice(1);\n return host === bare || host.endsWith(suffix);\n })\n ) {\n return true;\n }\n } catch {\n return true;\n }\n return false;\n}\n\nfunction isIpLiteralHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (host.includes(\":\")) return true;\n const parts = host.split(\".\");\n return parts.length === 4 && parts.every((p) => /^\\d+$/.test(p));\n}\n\n/**\n * Async SSRF guard for environments that can resolve DNS. The synchronous\n * guard catches literals and known rebinding domains; this closes the common\n * \"public hostname resolves to a private address\" gap before dispatch.\n */\nexport async function isBlockedExtensionUrlWithDns(\n url: string,\n): Promise<boolean> {\n if (isBlockedExtensionUrl(url)) return true;\n\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return true;\n }\n if (!hostname || isIpLiteralHost(hostname)) return false;\n\n try {\n const { lookup } = await import(\"node:dns/promises\");\n const records = await lookup(hostname, { all: true, verbatim: true });\n return records.some((record) => isPrivateHost(record.address));\n } catch {\n // Some edge runtimes do not expose DNS lookup. Keep the deterministic\n // parser-based protections instead of failing every outbound request.\n return false;\n }\n}\n\n/**\n * Build an undici Dispatcher whose connect-time DNS lookup runs through a\n * private-IP guard. This closes the TOCTOU gap where:\n * 1. We resolve hostname → public IP and pass.\n * 2. Between that lookup and the actual connect, DNS rebinding flips the\n * record to a private IP.\n * 3. fetch() resolves again and connects to the private IP.\n *\n * With a custom dispatcher, the same lookup that produces the IP also gates\n * the connect: if the IP is in the private set, the connect throws.\n *\n * Returns `null` if undici / node:dns are not available (e.g. some edge\n * runtimes); the caller should fall back to the regular `fetch` path —\n * `isBlockedExtensionUrlWithDns` will still have caught most rebinding cases.\n */\nexport async function createSsrfSafeDispatcher(): Promise<unknown | null> {\n // Keep the optional undici import opaque to Vite/Rolldown. A static\n // `import(\"undici\")` makes browser builds try to resolve and bundle undici\n // even though this dispatcher is only useful in Node server runtimes.\n let undici: any;\n let dnsModule: any;\n try {\n const runtimeImport = new Function(\n \"specifier\",\n \"return import(specifier)\",\n ) as (specifier: string) => Promise<any>;\n undici = await runtimeImport(\"undici\");\n dnsModule = await import(\"node:dns\");\n } catch {\n return null;\n }\n\n const { Agent } = undici;\n const { lookup } = dnsModule;\n if (!Agent || !lookup) return null;\n\n return new Agent({\n connect: {\n // Override DNS lookup at connect time so the IP we hand to undici's\n // socket is the one we authorized. Reject any record in the private\n // set BEFORE the TCP handshake.\n lookup: (\n hostname: string,\n options: any,\n callback: (\n err: NodeJS.ErrnoException | null,\n address?: string | { address: string; family: number }[],\n family?: number,\n ) => void,\n ) => {\n lookup(\n hostname,\n { all: true, verbatim: true },\n (err: NodeJS.ErrnoException | null, addresses: any) => {\n if (err) return callback(err);\n const list: { address: string; family: number }[] = Array.isArray(\n addresses,\n )\n ? addresses\n : [{ address: addresses, family: 4 }];\n for (const record of list) {\n if (isPrivateHost(record.address)) {\n const e = new Error(\n `Connect blocked: ${hostname} resolved to private address ${record.address}`,\n ) as NodeJS.ErrnoException;\n e.code = \"EAI_BLOCKED\";\n return callback(e);\n }\n }\n // Mirror Node's lookup behavior: when `all` is true, return the\n // array; otherwise the first entry. undici's connect honors\n // `options.all`.\n if (options && options.all) {\n return callback(null, list as any);\n }\n const first = list[0];\n return callback(null, first.address, first.family);\n },\n );\n },\n },\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Legacy aliases — predate the Tools → Extensions rename. Templates import\n// these via the legacy `@agent-native/core/tools/url-safety` subpath; keep\n// the names exported so they keep resolving until every consumer updates.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport { isBlockedExtensionUrl as isBlockedToolUrl };\nexport { isBlockedExtensionUrlWithDns as isBlockedToolUrlWithDns };\n"]}
1
+ {"version":3,"file":"url-safety.js","sourceRoot":"","sources":["../../src/extensions/url-safety.ts"],"names":[],"mappings":"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","sourcesContent":["const METADATA_HOSTS = [\n \"metadata.google.internal\",\n \"metadata.google.internal.\",\n];\n\nconst DNS_REBIND_SUFFIXES = [\n \".nip.io\",\n \".sslip.io\",\n \".xip.io\",\n \".localtest.me\",\n \".lvh.me\",\n];\n\nfunction isPrivateIpv4(a: number, b: number, c = 0, d = 0): boolean {\n if (![a, b, c, d].every((part) => part >= 0 && part <= 255)) return true;\n if (a === 127) return true;\n if (a === 10) return true;\n if (a === 172 && b >= 16 && b <= 31) return true;\n if (a === 192 && b === 168) return true;\n if (a === 169 && b === 254) return true;\n if (a === 0) return true;\n if (a === 100 && b >= 64 && b <= 127) return true;\n if (a === 192 && b === 0) return true;\n if (a === 198 && (b === 18 || b === 19)) return true;\n if (a === 192 && b === 0 && c === 2) return true;\n if (a === 198 && b === 51 && c === 100) return true;\n if (a === 203 && b === 0 && c === 113) return true;\n if (a >= 224) return true;\n return false;\n}\n\nfunction isPrivateIpv4MappedHex(host: string): boolean {\n const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);\n if (!mapped) return false;\n const high = Number.parseInt(mapped[1], 16);\n const low = Number.parseInt(mapped[2], 16);\n if (high < 0 || high > 0xffff || low < 0 || low > 0xffff) return false;\n const a = (high >> 8) & 0xff;\n const b = high & 0xff;\n const c = (low >> 8) & 0xff;\n const d = low & 0xff;\n return isPrivateIpv4(a, b, c, d);\n}\n\nfunction isPrivateHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (\n host === \"localhost\" ||\n host === \"::1\" ||\n host === \"::0\" ||\n host === \"::\"\n ) {\n return true;\n }\n if (METADATA_HOSTS.includes(host)) return true;\n\n // IPv6 ULA/link-local/multicast.\n if (/^f[cd]/.test(host) || /^fe[89ab]/.test(host)) return true;\n if (/^ff/i.test(host)) return true;\n\n // IPv4-mapped IPv6. URL parsing may preserve dotted form in some runtimes\n // or normalize it to hex, e.g. [::ffff:127.0.0.1] -> ::ffff:7f00:1.\n const v4mappedDotted = host.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (v4mappedDotted) {\n const [a, b, c, d] = v4mappedDotted[1].split(\".\").map(Number);\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n if (isPrivateIpv4MappedHex(host)) return true;\n\n // Dotted IPv4. URL parsing normalizes shorthand/octal/hex IPv4 forms to\n // dotted decimal before we reach this point.\n const parts = host.split(\".\");\n if (parts.length === 4 && parts.every((p) => /^\\d+$/.test(p))) {\n const [a, b, c, d] = parts.map(Number);\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n\n // Decimal integer IPv4.\n if (/^\\d+$/.test(host)) {\n const num = Number(host);\n if (num >= 0 && num <= 0xffffffff) {\n const a = (num >>> 24) & 0xff;\n const b = (num >>> 16) & 0xff;\n const c = (num >>> 8) & 0xff;\n const d = num & 0xff;\n if (isPrivateIpv4(a, b, c, d)) return true;\n }\n }\n\n return false;\n}\n\nexport function isBlockedExtensionUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return true;\n }\n const host = parsed.hostname.toLowerCase();\n if (isPrivateHost(host)) return true;\n if (\n DNS_REBIND_SUFFIXES.some((suffix) => {\n const bare = suffix.slice(1);\n return host === bare || host.endsWith(suffix);\n })\n ) {\n return true;\n }\n } catch {\n return true;\n }\n return false;\n}\n\nfunction isIpLiteralHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (host.includes(\":\")) return true;\n const parts = host.split(\".\");\n return parts.length === 4 && parts.every((p) => /^\\d+$/.test(p));\n}\n\n/**\n * Async SSRF guard for environments that can resolve DNS. The synchronous\n * guard catches literals and known rebinding domains; this closes the common\n * \"public hostname resolves to a private address\" gap before dispatch.\n */\nexport async function isBlockedExtensionUrlWithDns(\n url: string,\n): Promise<boolean> {\n if (isBlockedExtensionUrl(url)) return true;\n\n let hostname: string;\n try {\n hostname = new URL(url).hostname.toLowerCase();\n } catch {\n return true;\n }\n if (!hostname || isIpLiteralHost(hostname)) return false;\n\n try {\n const { lookup } = await import(\"node:dns/promises\");\n const records = await lookup(hostname, { all: true, verbatim: true });\n return records.some((record) => isPrivateHost(record.address));\n } catch {\n // Some edge runtimes do not expose DNS lookup. Keep the deterministic\n // parser-based protections instead of failing every outbound request.\n return false;\n }\n}\n\n/**\n * Build an undici Dispatcher whose connect-time DNS lookup runs through a\n * private-IP guard. This closes the TOCTOU gap where:\n * 1. We resolve hostname → public IP and pass.\n * 2. Between that lookup and the actual connect, DNS rebinding flips the\n * record to a private IP.\n * 3. fetch() resolves again and connects to the private IP.\n *\n * With a custom dispatcher, the same lookup that produces the IP also gates\n * the connect: if the IP is in the private set, the connect throws.\n *\n * Returns `null` if undici / node:dns are not available (e.g. some edge\n * runtimes); the caller should fall back to the regular `fetch` path —\n * `isBlockedExtensionUrlWithDns` will still have caught most rebinding cases.\n */\nexport async function createSsrfSafeDispatcher(): Promise<unknown | null> {\n // Keep the optional undici import opaque to Vite/Rolldown. A static\n // `import(\"undici\")` makes browser builds try to resolve and bundle undici\n // even though this dispatcher is only useful in Node server runtimes.\n let undici: any;\n let dnsModule: any;\n try {\n const runtimeImport = new Function(\n \"specifier\",\n \"return import(specifier)\",\n ) as (specifier: string) => Promise<any>;\n undici = await runtimeImport(\"undici\");\n dnsModule = await import(\"node:dns\");\n } catch {\n return null;\n }\n\n const { Agent } = undici;\n const { lookup } = dnsModule;\n if (!Agent || !lookup) return null;\n\n return new Agent({\n connect: {\n // Override DNS lookup at connect time so the IP we hand to undici's\n // socket is the one we authorized. Reject any record in the private\n // set BEFORE the TCP handshake.\n lookup: (\n hostname: string,\n options: any,\n callback: (\n err: NodeJS.ErrnoException | null,\n address?: string | { address: string; family: number }[],\n family?: number,\n ) => void,\n ) => {\n lookup(\n hostname,\n { all: true, verbatim: true },\n (err: NodeJS.ErrnoException | null, addresses: any) => {\n if (err) return callback(err);\n const list: { address: string; family: number }[] = Array.isArray(\n addresses,\n )\n ? addresses\n : [{ address: addresses, family: 4 }];\n for (const record of list) {\n if (isPrivateHost(record.address)) {\n const e = new Error(\n `Connect blocked: ${hostname} resolved to private address ${record.address}`,\n ) as NodeJS.ErrnoException;\n e.code = \"EAI_BLOCKED\";\n return callback(e);\n }\n }\n // Mirror Node's lookup behavior: when `all` is true, return the\n // array; otherwise the first entry. undici's connect honors\n // `options.all`.\n if (options && options.all) {\n return callback(null, list as any);\n }\n const first = list[0];\n return callback(null, first.address, first.family);\n },\n );\n },\n },\n });\n}\n\n/**\n * SSRF-safe `fetch` for any server-side request to a user/agent-supplied URL.\n *\n * Applies the same protections the extension proxy uses, so every call site\n * that fetches an untrusted URL gets them without re-implementing the loop:\n * 1. Pre-flight DNS-aware private-address check (isBlockedExtensionUrlWithDns)\n * on the initial URL and on every redirect hop.\n * 2. A connect-time dispatcher that re-checks the resolved IP at TCP-connect\n * time (closes the DNS-rebinding TOCTOU) when undici is available.\n * 3. Manual redirect handling — a public URL cannot 30x-redirect into the\n * private network because each hop is re-validated before it is followed.\n *\n * Throws an Error whose message starts with \"SSRF blocked:\" when a target\n * (initial or via redirect) resolves to a private/internal address, or when the\n * redirect limit is exceeded. Otherwise returns the final Response.\n */\nexport async function ssrfSafeFetch(\n url: string,\n init: RequestInit = {},\n options: { maxRedirects?: number } = {},\n): Promise<Response> {\n const maxRedirects = options.maxRedirects ?? 3;\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n\n let currentUrl = url;\n for (let hop = 0; hop <= maxRedirects; hop++) {\n if (await isBlockedExtensionUrlWithDns(currentUrl)) {\n throw new Error(\n `SSRF blocked: refusing to fetch private/internal address (${currentUrl})`,\n );\n }\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n ...init,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n\n const response = await fetch(currentUrl, fetchOpts);\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n if (!location) return response;\n currentUrl = new URL(location, currentUrl).href;\n continue;\n }\n return response;\n }\n throw new Error(\n `SSRF blocked: too many redirects (>${maxRedirects}) while fetching ${url}`,\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Legacy aliases — predate the Tools → Extensions rename. Templates import\n// these via the legacy `@agent-native/core/tools/url-safety` subpath; keep\n// the names exported so they keep resolving until every consumer updates.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport { isBlockedExtensionUrl as isBlockedToolUrl };\nexport { isBlockedExtensionUrlWithDns as isBlockedToolUrlWithDns };\nexport { ssrfSafeFetch as ssrfSafeToolFetch };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"upload-image.d.ts","sourceRoot":"","sources":["../../../src/file-upload/actions/upload-image.ts"],"names":[],"mappings":";AA6FA,wBAwEG"}
1
+ {"version":3,"file":"upload-image.d.ts","sourceRoot":"","sources":["../../../src/file-upload/actions/upload-image.ts"],"names":[],"mappings":";AAkGA,wBAwEG"}
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineAction } from "../../action.js";
3
3
  import { getRequestUserEmail } from "../../server/request-context.js";
4
+ import { ssrfSafeFetch } from "../../extensions/url-safety.js";
4
5
  import { uploadFile } from "../registry.js";
5
6
  const MAX_REMOTE_FETCH_BYTES = 25 * 1024 * 1024;
6
7
  const SUPPORTED_IMAGE_MIME_TYPES = new Set([
@@ -61,7 +62,11 @@ async function fetchRemote(url) {
61
62
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
62
63
  throw new Error("url must use http(s)");
63
64
  }
64
- const response = await fetch(url);
65
+ // SSRF guard: this URL is agent/user-controlled and the fetched bytes are
66
+ // re-hosted and returned, so an unguarded fetch is a full-read SSRF (cloud
67
+ // metadata, localhost, internal services). ssrfSafeFetch blocks private
68
+ // targets, re-checks at connect time, and re-validates every redirect hop.
69
+ const response = await ssrfSafeFetch(url, {}, { maxRedirects: 3 });
65
70
  if (!response.ok) {
66
71
  throw new Error(`Failed to fetch image (${response.status} ${response.statusText})`);
67
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"upload-image.js","sourceRoot":"","sources":["../../../src/file-upload/actions/upload-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport { defineAction } from \"../../action.js\";\nimport { getRequestUserEmail } from \"../../server/request-context.js\";\nimport { uploadFile } from \"../registry.js\";\n\nconst MAX_REMOTE_FETCH_BYTES = 25 * 1024 * 1024;\n\nconst SUPPORTED_IMAGE_MIME_TYPES = new Set([\n \"image/png\",\n \"image/jpeg\",\n \"image/jpg\",\n \"image/gif\",\n \"image/webp\",\n \"image/avif\",\n \"image/svg+xml\",\n \"image/heic\",\n \"image/heif\",\n]);\n\nfunction extensionFromMime(mimeType: string): string {\n const bare = mimeType.split(\";\")[0].trim().toLowerCase();\n if (bare === \"image/jpeg\" || bare === \"image/jpg\") return \".jpg\";\n if (bare === \"image/png\") return \".png\";\n if (bare === \"image/gif\") return \".gif\";\n if (bare === \"image/webp\") return \".webp\";\n if (bare === \"image/avif\") return \".avif\";\n if (bare === \"image/svg+xml\") return \".svg\";\n if (bare === \"image/heic\") return \".heic\";\n if (bare === \"image/heif\") return \".heif\";\n return \"\";\n}\n\nfunction defaultFilename(mimeType: string): string {\n return `image-${Date.now()}${extensionFromMime(mimeType) || \".bin\"}`;\n}\n\nfunction parseDataUrl(dataUrl: string): {\n bytes: Uint8Array;\n mimeType: string;\n} {\n const match = dataUrl.match(/^data:([^;,]+)(;base64)?,(.+)$/);\n if (!match) {\n throw new Error(\"data must be a data URL (data:image/...;base64,...)\");\n }\n const mimeType = match[1].trim().toLowerCase();\n const isBase64 = !!match[2];\n const payload = match[3];\n const bytes = isBase64\n ? new Uint8Array(Buffer.from(payload, \"base64\"))\n : new TextEncoder().encode(decodeURIComponent(payload));\n return { bytes, mimeType };\n}\n\nasync function fetchRemote(url: string): Promise<{\n bytes: Uint8Array;\n mimeType: string;\n}> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`url is not a valid URL: ${url}`);\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\"url must use http(s)\");\n }\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image (${response.status} ${response.statusText})`,\n );\n }\n const contentType = response.headers.get(\"content-type\") || \"\";\n const mimeType =\n contentType.split(\";\")[0].trim().toLowerCase() ||\n \"application/octet-stream\";\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.byteLength > MAX_REMOTE_FETCH_BYTES) {\n throw new Error(\n `Image too large (${buffer.byteLength} bytes, max ${MAX_REMOTE_FETCH_BYTES})`,\n );\n }\n return { bytes: new Uint8Array(buffer), mimeType };\n}\n\nfunction uploadNotConfiguredError(): string {\n return [\n \"Image uploads are not configured for this app.\",\n \"Configure a file upload provider — connect Builder.io in Settings → File uploads (free credits), set BUILDER_PRIVATE_KEY, or register a custom provider (S3, R2, GCS, etc.) via registerFileUploadProvider().\",\n ].join(\" \");\n}\n\nexport default defineAction({\n description:\n \"Upload an image to the configured file-upload provider (Builder.io by default) and return a hosted CDN URL. \" +\n \"Use this to turn a base64 data URL, a chat-attached image, or a transient remote URL into a stable URL that \" +\n 'can be embedded in <img src=\"...\">, slide HTML, documents, or shared with other apps. Falls back to a clear ' +\n \"'connect Builder.io' message when no provider is configured.\",\n schema: z\n .object({\n data: z\n .string()\n .optional()\n .describe(\n \"Base64 data URL (data:image/png;base64,...). Pass when the image bytes are already in the chat context — for example an attached or generated image. Either `data` or `url` is required.\",\n ),\n url: z\n .string()\n .optional()\n .describe(\n \"Remote image URL to re-host. Useful for preserving transient generated images, third-party search results, or any external URL whose long-term availability you don't control. Either `data` or `url` is required.\",\n ),\n filename: z\n .string()\n .optional()\n .describe(\n \"Optional filename hint, used by the provider for display and to derive an extension when missing.\",\n ),\n })\n .refine((args) => !!args.data || !!args.url, {\n message: \"Either `data` or `url` is required.\",\n }),\n run: async (args) => {\n let bytes: Uint8Array;\n let mimeType: string;\n\n if (args.data) {\n ({ bytes, mimeType } = parseDataUrl(args.data));\n } else if (args.url) {\n ({ bytes, mimeType } = await fetchRemote(args.url));\n } else {\n return { error: \"Either `data` or `url` is required.\" };\n }\n\n if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {\n return {\n error: `Unsupported image type: ${mimeType}. Supported: ${[...SUPPORTED_IMAGE_MIME_TYPES].join(\", \")}.`,\n };\n }\n\n const filename = (args.filename || defaultFilename(mimeType)).trim();\n const ownerEmail = getRequestUserEmail() ?? undefined;\n\n const result = await uploadFile({\n data: bytes,\n filename,\n mimeType,\n ownerEmail,\n });\n\n if (!result) {\n return {\n error: uploadNotConfiguredError(),\n configured: false,\n connectPath: \"/_agent-native/builder/connect\",\n };\n }\n\n return {\n url: result.url,\n id: result.id,\n provider: result.provider,\n };\n },\n});\n"]}
1
+ {"version":3,"file":"upload-image.js","sourceRoot":"","sources":["../../../src/file-upload/actions/upload-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport { defineAction } from \"../../action.js\";\nimport { getRequestUserEmail } from \"../../server/request-context.js\";\nimport { ssrfSafeFetch } from \"../../extensions/url-safety.js\";\nimport { uploadFile } from \"../registry.js\";\n\nconst MAX_REMOTE_FETCH_BYTES = 25 * 1024 * 1024;\n\nconst SUPPORTED_IMAGE_MIME_TYPES = new Set([\n \"image/png\",\n \"image/jpeg\",\n \"image/jpg\",\n \"image/gif\",\n \"image/webp\",\n \"image/avif\",\n \"image/svg+xml\",\n \"image/heic\",\n \"image/heif\",\n]);\n\nfunction extensionFromMime(mimeType: string): string {\n const bare = mimeType.split(\";\")[0].trim().toLowerCase();\n if (bare === \"image/jpeg\" || bare === \"image/jpg\") return \".jpg\";\n if (bare === \"image/png\") return \".png\";\n if (bare === \"image/gif\") return \".gif\";\n if (bare === \"image/webp\") return \".webp\";\n if (bare === \"image/avif\") return \".avif\";\n if (bare === \"image/svg+xml\") return \".svg\";\n if (bare === \"image/heic\") return \".heic\";\n if (bare === \"image/heif\") return \".heif\";\n return \"\";\n}\n\nfunction defaultFilename(mimeType: string): string {\n return `image-${Date.now()}${extensionFromMime(mimeType) || \".bin\"}`;\n}\n\nfunction parseDataUrl(dataUrl: string): {\n bytes: Uint8Array;\n mimeType: string;\n} {\n const match = dataUrl.match(/^data:([^;,]+)(;base64)?,(.+)$/);\n if (!match) {\n throw new Error(\"data must be a data URL (data:image/...;base64,...)\");\n }\n const mimeType = match[1].trim().toLowerCase();\n const isBase64 = !!match[2];\n const payload = match[3];\n const bytes = isBase64\n ? new Uint8Array(Buffer.from(payload, \"base64\"))\n : new TextEncoder().encode(decodeURIComponent(payload));\n return { bytes, mimeType };\n}\n\nasync function fetchRemote(url: string): Promise<{\n bytes: Uint8Array;\n mimeType: string;\n}> {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`url is not a valid URL: ${url}`);\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\"url must use http(s)\");\n }\n\n // SSRF guard: this URL is agent/user-controlled and the fetched bytes are\n // re-hosted and returned, so an unguarded fetch is a full-read SSRF (cloud\n // metadata, localhost, internal services). ssrfSafeFetch blocks private\n // targets, re-checks at connect time, and re-validates every redirect hop.\n const response = await ssrfSafeFetch(url, {}, { maxRedirects: 3 });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image (${response.status} ${response.statusText})`,\n );\n }\n const contentType = response.headers.get(\"content-type\") || \"\";\n const mimeType =\n contentType.split(\";\")[0].trim().toLowerCase() ||\n \"application/octet-stream\";\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.byteLength > MAX_REMOTE_FETCH_BYTES) {\n throw new Error(\n `Image too large (${buffer.byteLength} bytes, max ${MAX_REMOTE_FETCH_BYTES})`,\n );\n }\n return { bytes: new Uint8Array(buffer), mimeType };\n}\n\nfunction uploadNotConfiguredError(): string {\n return [\n \"Image uploads are not configured for this app.\",\n \"Configure a file upload provider — connect Builder.io in Settings → File uploads (free credits), set BUILDER_PRIVATE_KEY, or register a custom provider (S3, R2, GCS, etc.) via registerFileUploadProvider().\",\n ].join(\" \");\n}\n\nexport default defineAction({\n description:\n \"Upload an image to the configured file-upload provider (Builder.io by default) and return a hosted CDN URL. \" +\n \"Use this to turn a base64 data URL, a chat-attached image, or a transient remote URL into a stable URL that \" +\n 'can be embedded in <img src=\"...\">, slide HTML, documents, or shared with other apps. Falls back to a clear ' +\n \"'connect Builder.io' message when no provider is configured.\",\n schema: z\n .object({\n data: z\n .string()\n .optional()\n .describe(\n \"Base64 data URL (data:image/png;base64,...). Pass when the image bytes are already in the chat context — for example an attached or generated image. Either `data` or `url` is required.\",\n ),\n url: z\n .string()\n .optional()\n .describe(\n \"Remote image URL to re-host. Useful for preserving transient generated images, third-party search results, or any external URL whose long-term availability you don't control. Either `data` or `url` is required.\",\n ),\n filename: z\n .string()\n .optional()\n .describe(\n \"Optional filename hint, used by the provider for display and to derive an extension when missing.\",\n ),\n })\n .refine((args) => !!args.data || !!args.url, {\n message: \"Either `data` or `url` is required.\",\n }),\n run: async (args) => {\n let bytes: Uint8Array;\n let mimeType: string;\n\n if (args.data) {\n ({ bytes, mimeType } = parseDataUrl(args.data));\n } else if (args.url) {\n ({ bytes, mimeType } = await fetchRemote(args.url));\n } else {\n return { error: \"Either `data` or `url` is required.\" };\n }\n\n if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {\n return {\n error: `Unsupported image type: ${mimeType}. Supported: ${[...SUPPORTED_IMAGE_MIME_TYPES].join(\", \")}.`,\n };\n }\n\n const filename = (args.filename || defaultFilename(mimeType)).trim();\n const ownerEmail = getRequestUserEmail() ?? undefined;\n\n const result = await uploadFile({\n data: bytes,\n filename,\n mimeType,\n ownerEmail,\n });\n\n if (!result) {\n return {\n error: uploadNotConfiguredError(),\n configured: false,\n connectPath: \"/_agent-native/builder/connect\",\n };\n }\n\n return {\n url: result.url,\n id: result.id,\n provider: result.provider,\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/email.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,aAAa,CAAC;AAwCrB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAkR9C"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/email.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,aAAa,CAAC;AAwCrB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAwR9C"}