notte-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/proxy/core.ts
21
+ var core_exports = {};
22
+ __export(core_exports, {
23
+ handleProxyRequest: () => handleProxyRequest
24
+ });
25
+ module.exports = __toCommonJS(core_exports);
26
+
27
+ // src/proxy/patterns.ts
28
+ var DEFAULT_ALLOWED_PATTERNS = [
29
+ // Agents endpoints
30
+ /^agents$/,
31
+ // /agents
32
+ /^agents\/[^\/]+$/,
33
+ // /agents/{agent_id}
34
+ /^agents\/[^\/]+\/stop$/,
35
+ // /agents/{agent_id}/stop
36
+ /^agents\/[^\/]+\/workflow\/code$/,
37
+ // /agents/{agent_id}/workflow/code
38
+ /^agents\/start$/,
39
+ // /agents/start
40
+ // Anything endpoints
41
+ /^anything\/start$/,
42
+ // /anything/start
43
+ // Functions endpoints
44
+ /^functions$/,
45
+ // /functions
46
+ /^functions\/[^\/]+$/,
47
+ // /functions/{function_id}
48
+ /^functions\/[^\/]+\/fork$/,
49
+ // /functions/{function_id}/fork
50
+ /^functions\/[^\/]+\/runs$/,
51
+ // /functions/{function_id}/runs
52
+ /^functions\/[^\/]+\/runs\/[^\/]+$/,
53
+ // /functions/{function_id}/runs/{run_id}
54
+ /^functions\/[^\/]+\/runs\/start$/,
55
+ // /functions/{function_id}/runs/start
56
+ /^functions\/[^\/]+\/schedule$/,
57
+ // /functions/{function_id}/schedule
58
+ // Personas endpoints
59
+ /^personas$/,
60
+ // /personas
61
+ /^personas\/[^\/]+$/,
62
+ // /personas/{persona_id}
63
+ /^personas\/[^\/]+\/emails$/,
64
+ // /personas/{persona_id}/emails
65
+ /^personas\/[^\/]+\/sms$/,
66
+ // /personas/{persona_id}/sms
67
+ /^personas\/create$/,
68
+ // /personas/create
69
+ // Profiles endpoints
70
+ /^profiles$/,
71
+ // /profiles
72
+ /^profiles\/[^\/]+$/,
73
+ // /profiles/{profile_id}
74
+ /^profiles\/create$/,
75
+ // /profiles/create
76
+ // Prompts endpoints
77
+ /^prompts\/improve$/,
78
+ // /prompts/improve
79
+ /^prompts\/nudge$/,
80
+ // /prompts/nudge
81
+ // Root endpoints
82
+ /^health$/,
83
+ // /health
84
+ /^scrape$/,
85
+ // /scrape
86
+ /^scrape_from_html$/,
87
+ // /scrape_from_html
88
+ // Sessions endpoints
89
+ /^sessions$/,
90
+ // /sessions
91
+ /^sessions\/[^\/]+$/,
92
+ // /sessions/{session_id}
93
+ /^sessions\/[^\/]+\/cookies$/,
94
+ // /sessions/{session_id}/cookies
95
+ /^sessions\/[^\/]+\/debug$/,
96
+ // /sessions/{session_id}/debug
97
+ /^sessions\/[^\/]+\/network\/logs$/,
98
+ // /sessions/{session_id}/network/logs
99
+ /^sessions\/[^\/]+\/offset$/,
100
+ // /sessions/{session_id}/offset
101
+ /^sessions\/[^\/]+\/page\/execute$/,
102
+ // /sessions/{session_id}/page/execute
103
+ /^sessions\/[^\/]+\/page\/observe$/,
104
+ // /sessions/{session_id}/page/observe
105
+ /^sessions\/[^\/]+\/page\/scrape$/,
106
+ // /sessions/{session_id}/page/scrape
107
+ /^sessions\/[^\/]+\/page\/screenshot$/,
108
+ // /sessions/{session_id}/page/screenshot
109
+ /^sessions\/[^\/]+\/replay$/,
110
+ // /sessions/{session_id}/replay
111
+ /^sessions\/[^\/]+\/stop$/,
112
+ // /sessions/{session_id}/stop
113
+ /^sessions\/[^\/]+\/workflow\/code$/,
114
+ // /sessions/{session_id}/workflow/code
115
+ /^sessions\/start$/,
116
+ // /sessions/start
117
+ // Storage endpoints
118
+ /^storage\/[^\/]+\/downloads$/,
119
+ // /storage/{session_id}/downloads
120
+ /^storage\/[^\/]+\/downloads\/[^\/]+$/,
121
+ // /storage/{session_id}/downloads/{filename}
122
+ /^storage\/uploads$/,
123
+ // /storage/uploads
124
+ /^storage\/uploads\/[^\/]+$/,
125
+ // /storage/uploads/{filename}
126
+ // Usage endpoints
127
+ /^usage$/,
128
+ // /usage
129
+ /^usage\/logs$/,
130
+ // /usage/logs
131
+ // Vaults endpoints
132
+ /^vaults$/,
133
+ // /vaults
134
+ /^vaults\/[^\/]+$/,
135
+ // /vaults/{vault_id}
136
+ /^vaults\/[^\/]+\/card$/,
137
+ // /vaults/{vault_id}/card
138
+ /^vaults\/[^\/]+\/credentials$/,
139
+ // /vaults/{vault_id}/credentials
140
+ /^vaults\/create$/
141
+ // /vaults/create
142
+ ];
143
+ function validatePath(pathSegments, allowedPatterns) {
144
+ const path = pathSegments.join("/");
145
+ if (path.includes("..") || path.includes("//") || path.startsWith("/")) {
146
+ return {
147
+ isValid: false,
148
+ error: "Invalid path: directory traversal detected"
149
+ };
150
+ }
151
+ const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));
152
+ if (!isValidPattern) {
153
+ return {
154
+ isValid: false,
155
+ error: `Invalid path: ${path} does not match any allowed endpoint pattern`
156
+ };
157
+ }
158
+ return { isValid: true };
159
+ }
160
+
161
+ // src/proxy/types.ts
162
+ var NotteProxyAuthError = class extends Error {
163
+ constructor(message = "Unauthorized") {
164
+ super(message);
165
+ this.name = "NotteProxyAuthError";
166
+ }
167
+ };
168
+
169
+ // src/proxy/core.ts
170
+ var DEFAULT_API_URL = "https://api.notte.cc";
171
+ var DEFAULT_FORWARD_HEADERS = ["content-type", "accept"];
172
+ var METHODS_WITH_BODY = ["POST", "PUT", "PATCH"];
173
+ async function handleProxyRequest(request, pathSegments, config) {
174
+ try {
175
+ if (config.allowedPatterns !== false) {
176
+ const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;
177
+ const pathValidation = validatePath(pathSegments, patterns);
178
+ if (!pathValidation.isValid) {
179
+ return new Response(
180
+ JSON.stringify({
181
+ error: "Invalid API endpoint",
182
+ details: pathValidation.error
183
+ }),
184
+ {
185
+ status: 400,
186
+ headers: { "Content-Type": "application/json" }
187
+ }
188
+ );
189
+ }
190
+ }
191
+ let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
192
+ if (config.authenticate) {
193
+ const result = await config.authenticate(request);
194
+ if (typeof result === "string") {
195
+ apiKey = result;
196
+ }
197
+ }
198
+ if (!apiKey) {
199
+ return new Response(
200
+ JSON.stringify({ error: "No API key configured" }),
201
+ {
202
+ status: 401,
203
+ headers: { "Content-Type": "application/json" }
204
+ }
205
+ );
206
+ }
207
+ const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
208
+ const path = pathSegments.join("/");
209
+ const incomingUrl = new URL(request.url);
210
+ const searchParams = incomingUrl.searchParams.toString();
211
+ const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
212
+ const forwardHeaders = new Headers();
213
+ forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
214
+ forwardHeaders.set(
215
+ "x-notte-request-origin",
216
+ config.requestOrigin || "sdk-proxy"
217
+ );
218
+ const headersToForward = config.forwardHeaders || DEFAULT_FORWARD_HEADERS;
219
+ for (const header of headersToForward) {
220
+ const value = request.headers.get(header);
221
+ if (value) {
222
+ forwardHeaders.set(header, value);
223
+ }
224
+ }
225
+ if (config.onBeforeRequest) {
226
+ await config.onBeforeRequest({
227
+ path,
228
+ method: request.method,
229
+ headers: forwardHeaders,
230
+ url: targetUrl,
231
+ incomingRequest: request
232
+ });
233
+ }
234
+ const requestOptions = {
235
+ method: request.method,
236
+ headers: forwardHeaders
237
+ };
238
+ if (METHODS_WITH_BODY.includes(request.method) && request.body) {
239
+ requestOptions.body = request.body;
240
+ requestOptions.duplex = "half";
241
+ }
242
+ let response;
243
+ try {
244
+ response = await fetch(targetUrl, requestOptions);
245
+ } catch (fetchError) {
246
+ const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown fetch error";
247
+ return new Response(
248
+ JSON.stringify({
249
+ error: "Failed to forward request to Notte API",
250
+ details: errorMessage
251
+ }),
252
+ {
253
+ status: 502,
254
+ headers: { "Content-Type": "application/json" }
255
+ }
256
+ );
257
+ }
258
+ const contentType = response.headers.get("content-type") || "";
259
+ const isBinaryResponse = contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/") || contentType.includes("octet-stream");
260
+ const isStreamingResponse = contentType.includes("text/event-stream");
261
+ if (isStreamingResponse && response.body) {
262
+ return new Response(response.body, {
263
+ status: response.status,
264
+ statusText: response.statusText,
265
+ headers: {
266
+ "Content-Type": contentType,
267
+ "Cache-Control": "no-cache",
268
+ Connection: "keep-alive"
269
+ }
270
+ });
271
+ }
272
+ if (response.body) {
273
+ return new Response(response.body, {
274
+ status: response.status,
275
+ statusText: response.statusText,
276
+ headers: {
277
+ "Content-Type": contentType
278
+ }
279
+ });
280
+ }
281
+ if (isBinaryResponse) {
282
+ const data2 = await response.arrayBuffer();
283
+ return new Response(data2, {
284
+ status: response.status,
285
+ statusText: response.statusText,
286
+ headers: { "Content-Type": contentType }
287
+ });
288
+ }
289
+ const data = await response.text();
290
+ return new Response(data, {
291
+ status: response.status,
292
+ statusText: response.statusText,
293
+ headers: { "Content-Type": contentType }
294
+ });
295
+ } catch (error) {
296
+ if (error instanceof NotteProxyAuthError) {
297
+ return new Response(
298
+ JSON.stringify({ error: error.message }),
299
+ {
300
+ status: 401,
301
+ headers: { "Content-Type": "application/json" }
302
+ }
303
+ );
304
+ }
305
+ throw error;
306
+ }
307
+ }
308
+ // Annotate the CommonJS export names for ESM import in node:
309
+ 0 && (module.exports = {
310
+ handleProxyRequest
311
+ });
312
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/proxy/core.ts","../../src/proxy/patterns.ts","../../src/proxy/types.ts"],"sourcesContent":["import { validatePath, DEFAULT_ALLOWED_PATTERNS } from './patterns';\nimport type { NotteProxyConfig } from './types';\nimport { NotteProxyAuthError } from './types';\n\nconst DEFAULT_API_URL = 'https://api.notte.cc';\nconst DEFAULT_FORWARD_HEADERS = ['content-type', 'accept'];\nconst METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH'];\n\n/**\n * Framework-agnostic proxy handler that forwards requests to the Notte API.\n *\n * Takes a standard Web API `Request`, injects authentication, and returns\n * a standard Web API `Response`. Works with any runtime that supports\n * the Web API standards (Node 18+, Deno, Bun, Cloudflare Workers).\n *\n * @param request - The incoming HTTP request (standard Web API Request)\n * @param pathSegments - The path segments after the proxy base path (e.g., ['sessions', 'start'])\n * @param config - Proxy configuration including API key, auth hooks, etc.\n * @returns A standard Web API Response\n */\nexport async function handleProxyRequest(\n request: Request,\n pathSegments: string[],\n config: NotteProxyConfig,\n): Promise<Response> {\n try {\n // Step 1: Path validation\n if (config.allowedPatterns !== false) {\n const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;\n const pathValidation = validatePath(pathSegments, patterns);\n\n if (!pathValidation.isValid) {\n return new Response(\n JSON.stringify({\n error: 'Invalid API endpoint',\n details: pathValidation.error,\n }),\n {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n }\n\n // Step 2: Authentication\n let apiKey = config.apiKey || process.env.NOTTE_API_KEY;\n\n if (config.authenticate) {\n const result = await config.authenticate(request);\n if (typeof result === 'string') {\n apiKey = result;\n }\n }\n\n if (!apiKey) {\n return new Response(\n JSON.stringify({ error: 'No API key configured' }),\n {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Step 3: Build target URL\n const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\\/$/, '');\n const path = pathSegments.join('/');\n const incomingUrl = new URL(request.url);\n const searchParams = incomingUrl.searchParams.toString();\n const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ''}`;\n\n // Step 4: Build headers\n const forwardHeaders = new Headers();\n forwardHeaders.set('Authorization', `Bearer ${apiKey}`);\n forwardHeaders.set(\n 'x-notte-request-origin',\n config.requestOrigin || 'sdk-proxy',\n );\n\n const headersToForward =\n config.forwardHeaders || DEFAULT_FORWARD_HEADERS;\n for (const header of headersToForward) {\n const value = request.headers.get(header);\n if (value) {\n forwardHeaders.set(header, value);\n }\n }\n\n // Step 5: onBeforeRequest hook\n if (config.onBeforeRequest) {\n await config.onBeforeRequest({\n path,\n method: request.method,\n headers: forwardHeaders,\n url: targetUrl,\n incomingRequest: request,\n });\n }\n\n // Step 6: Forward the request\n const requestOptions: RequestInit = {\n method: request.method,\n headers: forwardHeaders,\n };\n\n if (METHODS_WITH_BODY.includes(request.method) && request.body) {\n requestOptions.body = request.body;\n (requestOptions as any).duplex = 'half';\n }\n\n let response: globalThis.Response;\n try {\n response = await fetch(targetUrl, requestOptions);\n } catch (fetchError) {\n const errorMessage =\n fetchError instanceof Error\n ? fetchError.message\n : 'Unknown fetch error';\n return new Response(\n JSON.stringify({\n error: 'Failed to forward request to Notte API',\n details: errorMessage,\n }),\n {\n status: 502,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Step 7: Handle response\n const contentType = response.headers.get('content-type') || '';\n\n const isBinaryResponse =\n contentType.startsWith('image/') ||\n contentType.startsWith('video/') ||\n contentType.startsWith('audio/') ||\n contentType.includes('octet-stream');\n\n const isStreamingResponse = contentType.includes('text/event-stream');\n\n // For streaming responses, pass through the body with appropriate headers\n if (isStreamingResponse && response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n }\n\n // For all other responses with a body stream, pass it through directly\n if (response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: {\n 'Content-Type': contentType,\n },\n });\n }\n\n // Fallback for responses without a body stream: buffer appropriately\n if (isBinaryResponse) {\n const data = await response.arrayBuffer();\n return new Response(data, {\n status: response.status,\n statusText: response.statusText,\n headers: { 'Content-Type': contentType },\n });\n }\n\n // Fallback: buffer as text\n const data = await response.text();\n return new Response(data, {\n status: response.status,\n statusText: response.statusText,\n headers: { 'Content-Type': contentType },\n });\n } catch (error) {\n // NotteProxyAuthError from authenticate() → 401\n if (error instanceof NotteProxyAuthError) {\n return new Response(\n JSON.stringify({ error: error.message }),\n {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n },\n );\n }\n\n // Re-throw unexpected errors so the adapter layer can handle them\n throw error;\n }\n}\n","/**\n * Auto-generated from sdk.gen.ts by scripts/generate-proxy-patterns.js\n * DO NOT EDIT MANUALLY — re-run `npm run generate` to update.\n *\n * Allowed API endpoint patterns to prevent SSRF attacks.\n * Every pattern corresponds to an endpoint in the Notte API OpenAPI spec.\n */\nexport const DEFAULT_ALLOWED_PATTERNS: RegExp[] = [\n // Agents endpoints\n /^agents$/, // /agents\n /^agents\\/[^\\/]+$/, // /agents/{agent_id}\n /^agents\\/[^\\/]+\\/stop$/, // /agents/{agent_id}/stop\n /^agents\\/[^\\/]+\\/workflow\\/code$/, // /agents/{agent_id}/workflow/code\n /^agents\\/start$/, // /agents/start\n\n // Anything endpoints\n /^anything\\/start$/, // /anything/start\n\n // Functions endpoints\n /^functions$/, // /functions\n /^functions\\/[^\\/]+$/, // /functions/{function_id}\n /^functions\\/[^\\/]+\\/fork$/, // /functions/{function_id}/fork\n /^functions\\/[^\\/]+\\/runs$/, // /functions/{function_id}/runs\n /^functions\\/[^\\/]+\\/runs\\/[^\\/]+$/, // /functions/{function_id}/runs/{run_id}\n /^functions\\/[^\\/]+\\/runs\\/start$/, // /functions/{function_id}/runs/start\n /^functions\\/[^\\/]+\\/schedule$/, // /functions/{function_id}/schedule\n\n // Personas endpoints\n /^personas$/, // /personas\n /^personas\\/[^\\/]+$/, // /personas/{persona_id}\n /^personas\\/[^\\/]+\\/emails$/, // /personas/{persona_id}/emails\n /^personas\\/[^\\/]+\\/sms$/, // /personas/{persona_id}/sms\n /^personas\\/create$/, // /personas/create\n\n // Profiles endpoints\n /^profiles$/, // /profiles\n /^profiles\\/[^\\/]+$/, // /profiles/{profile_id}\n /^profiles\\/create$/, // /profiles/create\n\n // Prompts endpoints\n /^prompts\\/improve$/, // /prompts/improve\n /^prompts\\/nudge$/, // /prompts/nudge\n\n // Root endpoints\n /^health$/, // /health\n /^scrape$/, // /scrape\n /^scrape_from_html$/, // /scrape_from_html\n\n // Sessions endpoints\n /^sessions$/, // /sessions\n /^sessions\\/[^\\/]+$/, // /sessions/{session_id}\n /^sessions\\/[^\\/]+\\/cookies$/, // /sessions/{session_id}/cookies\n /^sessions\\/[^\\/]+\\/debug$/, // /sessions/{session_id}/debug\n /^sessions\\/[^\\/]+\\/network\\/logs$/, // /sessions/{session_id}/network/logs\n /^sessions\\/[^\\/]+\\/offset$/, // /sessions/{session_id}/offset\n /^sessions\\/[^\\/]+\\/page\\/execute$/, // /sessions/{session_id}/page/execute\n /^sessions\\/[^\\/]+\\/page\\/observe$/, // /sessions/{session_id}/page/observe\n /^sessions\\/[^\\/]+\\/page\\/scrape$/, // /sessions/{session_id}/page/scrape\n /^sessions\\/[^\\/]+\\/page\\/screenshot$/, // /sessions/{session_id}/page/screenshot\n /^sessions\\/[^\\/]+\\/replay$/, // /sessions/{session_id}/replay\n /^sessions\\/[^\\/]+\\/stop$/, // /sessions/{session_id}/stop\n /^sessions\\/[^\\/]+\\/workflow\\/code$/, // /sessions/{session_id}/workflow/code\n /^sessions\\/start$/, // /sessions/start\n\n // Storage endpoints\n /^storage\\/[^\\/]+\\/downloads$/, // /storage/{session_id}/downloads\n /^storage\\/[^\\/]+\\/downloads\\/[^\\/]+$/, // /storage/{session_id}/downloads/{filename}\n /^storage\\/uploads$/, // /storage/uploads\n /^storage\\/uploads\\/[^\\/]+$/, // /storage/uploads/{filename}\n\n // Usage endpoints\n /^usage$/, // /usage\n /^usage\\/logs$/, // /usage/logs\n\n // Vaults endpoints\n /^vaults$/, // /vaults\n /^vaults\\/[^\\/]+$/, // /vaults/{vault_id}\n /^vaults\\/[^\\/]+\\/card$/, // /vaults/{vault_id}/card\n /^vaults\\/[^\\/]+\\/credentials$/, // /vaults/{vault_id}/credentials\n /^vaults\\/create$/, // /vaults/create\n];\n\nexport interface PathValidationResult {\n isValid: boolean;\n error?: string;\n}\n\n/**\n * Validates a request path against a list of allowed patterns.\n * Prevents directory traversal and SSRF attacks.\n */\nexport function validatePath(\n pathSegments: string[],\n allowedPatterns: RegExp[],\n): PathValidationResult {\n const path = pathSegments.join('/');\n\n // Check for directory traversal attempts\n if (path.includes('..') || path.includes('//') || path.startsWith('/')) {\n return {\n isValid: false,\n error: 'Invalid path: directory traversal detected',\n };\n }\n\n // Check against allowed patterns\n const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));\n if (!isValidPattern) {\n return {\n isValid: false,\n error: `Invalid path: ${path} does not match any allowed endpoint pattern`,\n };\n }\n\n return { isValid: true };\n}\n","/**\n * Error class for authentication/authorization failures in the proxy.\n * Throw this from `authenticate()` to return a 401 response.\n *\n * @example\n * ```typescript\n * authenticate: async (request) => {\n * const session = await getSession(request);\n * if (!session) throw new NotteProxyAuthError('Not logged in');\n * }\n * ```\n */\nexport class NotteProxyAuthError extends Error {\n constructor(message = 'Unauthorized') {\n super(message);\n this.name = 'NotteProxyAuthError';\n }\n}\n\n/**\n * Configuration for the Notte API proxy.\n *\n * The proxy forwards incoming HTTP requests to the Notte API,\n * injecting authentication headers server-side so the API key\n * is never exposed to the client.\n */\nexport interface NotteProxyConfig {\n /**\n * The Notte API key to use for all forwarded requests.\n * Falls back to the NOTTE_API_KEY environment variable when omitted.\n * Can be overridden per-request by returning a string from `authenticate`.\n */\n apiKey?: string;\n\n /**\n * Base URL of the Notte API.\n * @default 'https://api.notte.cc'\n */\n apiUrl?: string;\n\n /**\n * Optional async function to authenticate/authorize incoming requests.\n *\n * - Throw a `NotteProxyAuthError` to reject the request (returns 401).\n * - Return `void` to allow the request through using the default `apiKey`.\n * - Return a `string` to override the API key for this specific request\n * (useful for per-user API keys).\n */\n authenticate?: (request: Request) => Promise<void | string>;\n\n /**\n * Allowed API endpoint patterns for path validation (SSRF prevention).\n *\n * - `undefined` (default): uses the built-in allowlist derived from the OpenAPI spec.\n * - `RegExp[]`: custom list of allowed patterns.\n * - `false`: disables path validation entirely (not recommended for public-facing proxies).\n */\n allowedPatterns?: RegExp[] | false;\n\n /**\n * Value for the `x-notte-request-origin` header sent to the Notte API.\n * @default 'sdk-proxy'\n */\n requestOrigin?: string;\n\n /**\n * List of header names to forward from the incoming request to the Notte API.\n * @default ['content-type', 'accept']\n */\n forwardHeaders?: string[];\n\n /**\n * Optional hook called just before the request is forwarded to the Notte API.\n * Use this to modify outgoing headers, log requests, etc.\n */\n onBeforeRequest?: (ctx: {\n path: string;\n method: string;\n headers: Headers;\n url: string;\n incomingRequest: Request;\n }) => Promise<void> | void;\n}\n"],"mappings":"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","names":["data"]}
@@ -0,0 +1,285 @@
1
+ // src/proxy/patterns.ts
2
+ var DEFAULT_ALLOWED_PATTERNS = [
3
+ // Agents endpoints
4
+ /^agents$/,
5
+ // /agents
6
+ /^agents\/[^\/]+$/,
7
+ // /agents/{agent_id}
8
+ /^agents\/[^\/]+\/stop$/,
9
+ // /agents/{agent_id}/stop
10
+ /^agents\/[^\/]+\/workflow\/code$/,
11
+ // /agents/{agent_id}/workflow/code
12
+ /^agents\/start$/,
13
+ // /agents/start
14
+ // Anything endpoints
15
+ /^anything\/start$/,
16
+ // /anything/start
17
+ // Functions endpoints
18
+ /^functions$/,
19
+ // /functions
20
+ /^functions\/[^\/]+$/,
21
+ // /functions/{function_id}
22
+ /^functions\/[^\/]+\/fork$/,
23
+ // /functions/{function_id}/fork
24
+ /^functions\/[^\/]+\/runs$/,
25
+ // /functions/{function_id}/runs
26
+ /^functions\/[^\/]+\/runs\/[^\/]+$/,
27
+ // /functions/{function_id}/runs/{run_id}
28
+ /^functions\/[^\/]+\/runs\/start$/,
29
+ // /functions/{function_id}/runs/start
30
+ /^functions\/[^\/]+\/schedule$/,
31
+ // /functions/{function_id}/schedule
32
+ // Personas endpoints
33
+ /^personas$/,
34
+ // /personas
35
+ /^personas\/[^\/]+$/,
36
+ // /personas/{persona_id}
37
+ /^personas\/[^\/]+\/emails$/,
38
+ // /personas/{persona_id}/emails
39
+ /^personas\/[^\/]+\/sms$/,
40
+ // /personas/{persona_id}/sms
41
+ /^personas\/create$/,
42
+ // /personas/create
43
+ // Profiles endpoints
44
+ /^profiles$/,
45
+ // /profiles
46
+ /^profiles\/[^\/]+$/,
47
+ // /profiles/{profile_id}
48
+ /^profiles\/create$/,
49
+ // /profiles/create
50
+ // Prompts endpoints
51
+ /^prompts\/improve$/,
52
+ // /prompts/improve
53
+ /^prompts\/nudge$/,
54
+ // /prompts/nudge
55
+ // Root endpoints
56
+ /^health$/,
57
+ // /health
58
+ /^scrape$/,
59
+ // /scrape
60
+ /^scrape_from_html$/,
61
+ // /scrape_from_html
62
+ // Sessions endpoints
63
+ /^sessions$/,
64
+ // /sessions
65
+ /^sessions\/[^\/]+$/,
66
+ // /sessions/{session_id}
67
+ /^sessions\/[^\/]+\/cookies$/,
68
+ // /sessions/{session_id}/cookies
69
+ /^sessions\/[^\/]+\/debug$/,
70
+ // /sessions/{session_id}/debug
71
+ /^sessions\/[^\/]+\/network\/logs$/,
72
+ // /sessions/{session_id}/network/logs
73
+ /^sessions\/[^\/]+\/offset$/,
74
+ // /sessions/{session_id}/offset
75
+ /^sessions\/[^\/]+\/page\/execute$/,
76
+ // /sessions/{session_id}/page/execute
77
+ /^sessions\/[^\/]+\/page\/observe$/,
78
+ // /sessions/{session_id}/page/observe
79
+ /^sessions\/[^\/]+\/page\/scrape$/,
80
+ // /sessions/{session_id}/page/scrape
81
+ /^sessions\/[^\/]+\/page\/screenshot$/,
82
+ // /sessions/{session_id}/page/screenshot
83
+ /^sessions\/[^\/]+\/replay$/,
84
+ // /sessions/{session_id}/replay
85
+ /^sessions\/[^\/]+\/stop$/,
86
+ // /sessions/{session_id}/stop
87
+ /^sessions\/[^\/]+\/workflow\/code$/,
88
+ // /sessions/{session_id}/workflow/code
89
+ /^sessions\/start$/,
90
+ // /sessions/start
91
+ // Storage endpoints
92
+ /^storage\/[^\/]+\/downloads$/,
93
+ // /storage/{session_id}/downloads
94
+ /^storage\/[^\/]+\/downloads\/[^\/]+$/,
95
+ // /storage/{session_id}/downloads/{filename}
96
+ /^storage\/uploads$/,
97
+ // /storage/uploads
98
+ /^storage\/uploads\/[^\/]+$/,
99
+ // /storage/uploads/{filename}
100
+ // Usage endpoints
101
+ /^usage$/,
102
+ // /usage
103
+ /^usage\/logs$/,
104
+ // /usage/logs
105
+ // Vaults endpoints
106
+ /^vaults$/,
107
+ // /vaults
108
+ /^vaults\/[^\/]+$/,
109
+ // /vaults/{vault_id}
110
+ /^vaults\/[^\/]+\/card$/,
111
+ // /vaults/{vault_id}/card
112
+ /^vaults\/[^\/]+\/credentials$/,
113
+ // /vaults/{vault_id}/credentials
114
+ /^vaults\/create$/
115
+ // /vaults/create
116
+ ];
117
+ function validatePath(pathSegments, allowedPatterns) {
118
+ const path = pathSegments.join("/");
119
+ if (path.includes("..") || path.includes("//") || path.startsWith("/")) {
120
+ return {
121
+ isValid: false,
122
+ error: "Invalid path: directory traversal detected"
123
+ };
124
+ }
125
+ const isValidPattern = allowedPatterns.some((pattern) => pattern.test(path));
126
+ if (!isValidPattern) {
127
+ return {
128
+ isValid: false,
129
+ error: `Invalid path: ${path} does not match any allowed endpoint pattern`
130
+ };
131
+ }
132
+ return { isValid: true };
133
+ }
134
+
135
+ // src/proxy/types.ts
136
+ var NotteProxyAuthError = class extends Error {
137
+ constructor(message = "Unauthorized") {
138
+ super(message);
139
+ this.name = "NotteProxyAuthError";
140
+ }
141
+ };
142
+
143
+ // src/proxy/core.ts
144
+ var DEFAULT_API_URL = "https://api.notte.cc";
145
+ var DEFAULT_FORWARD_HEADERS = ["content-type", "accept"];
146
+ var METHODS_WITH_BODY = ["POST", "PUT", "PATCH"];
147
+ async function handleProxyRequest(request, pathSegments, config) {
148
+ try {
149
+ if (config.allowedPatterns !== false) {
150
+ const patterns = config.allowedPatterns || DEFAULT_ALLOWED_PATTERNS;
151
+ const pathValidation = validatePath(pathSegments, patterns);
152
+ if (!pathValidation.isValid) {
153
+ return new Response(
154
+ JSON.stringify({
155
+ error: "Invalid API endpoint",
156
+ details: pathValidation.error
157
+ }),
158
+ {
159
+ status: 400,
160
+ headers: { "Content-Type": "application/json" }
161
+ }
162
+ );
163
+ }
164
+ }
165
+ let apiKey = config.apiKey || process.env.NOTTE_API_KEY;
166
+ if (config.authenticate) {
167
+ const result = await config.authenticate(request);
168
+ if (typeof result === "string") {
169
+ apiKey = result;
170
+ }
171
+ }
172
+ if (!apiKey) {
173
+ return new Response(
174
+ JSON.stringify({ error: "No API key configured" }),
175
+ {
176
+ status: 401,
177
+ headers: { "Content-Type": "application/json" }
178
+ }
179
+ );
180
+ }
181
+ const apiUrl = (config.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
182
+ const path = pathSegments.join("/");
183
+ const incomingUrl = new URL(request.url);
184
+ const searchParams = incomingUrl.searchParams.toString();
185
+ const targetUrl = `${apiUrl}/${path}${searchParams ? `?${searchParams}` : ""}`;
186
+ const forwardHeaders = new Headers();
187
+ forwardHeaders.set("Authorization", `Bearer ${apiKey}`);
188
+ forwardHeaders.set(
189
+ "x-notte-request-origin",
190
+ config.requestOrigin || "sdk-proxy"
191
+ );
192
+ const headersToForward = config.forwardHeaders || DEFAULT_FORWARD_HEADERS;
193
+ for (const header of headersToForward) {
194
+ const value = request.headers.get(header);
195
+ if (value) {
196
+ forwardHeaders.set(header, value);
197
+ }
198
+ }
199
+ if (config.onBeforeRequest) {
200
+ await config.onBeforeRequest({
201
+ path,
202
+ method: request.method,
203
+ headers: forwardHeaders,
204
+ url: targetUrl,
205
+ incomingRequest: request
206
+ });
207
+ }
208
+ const requestOptions = {
209
+ method: request.method,
210
+ headers: forwardHeaders
211
+ };
212
+ if (METHODS_WITH_BODY.includes(request.method) && request.body) {
213
+ requestOptions.body = request.body;
214
+ requestOptions.duplex = "half";
215
+ }
216
+ let response;
217
+ try {
218
+ response = await fetch(targetUrl, requestOptions);
219
+ } catch (fetchError) {
220
+ const errorMessage = fetchError instanceof Error ? fetchError.message : "Unknown fetch error";
221
+ return new Response(
222
+ JSON.stringify({
223
+ error: "Failed to forward request to Notte API",
224
+ details: errorMessage
225
+ }),
226
+ {
227
+ status: 502,
228
+ headers: { "Content-Type": "application/json" }
229
+ }
230
+ );
231
+ }
232
+ const contentType = response.headers.get("content-type") || "";
233
+ const isBinaryResponse = contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/") || contentType.includes("octet-stream");
234
+ const isStreamingResponse = contentType.includes("text/event-stream");
235
+ if (isStreamingResponse && response.body) {
236
+ return new Response(response.body, {
237
+ status: response.status,
238
+ statusText: response.statusText,
239
+ headers: {
240
+ "Content-Type": contentType,
241
+ "Cache-Control": "no-cache",
242
+ Connection: "keep-alive"
243
+ }
244
+ });
245
+ }
246
+ if (response.body) {
247
+ return new Response(response.body, {
248
+ status: response.status,
249
+ statusText: response.statusText,
250
+ headers: {
251
+ "Content-Type": contentType
252
+ }
253
+ });
254
+ }
255
+ if (isBinaryResponse) {
256
+ const data2 = await response.arrayBuffer();
257
+ return new Response(data2, {
258
+ status: response.status,
259
+ statusText: response.statusText,
260
+ headers: { "Content-Type": contentType }
261
+ });
262
+ }
263
+ const data = await response.text();
264
+ return new Response(data, {
265
+ status: response.status,
266
+ statusText: response.statusText,
267
+ headers: { "Content-Type": contentType }
268
+ });
269
+ } catch (error) {
270
+ if (error instanceof NotteProxyAuthError) {
271
+ return new Response(
272
+ JSON.stringify({ error: error.message }),
273
+ {
274
+ status: 401,
275
+ headers: { "Content-Type": "application/json" }
276
+ }
277
+ );
278
+ }
279
+ throw error;
280
+ }
281
+ }
282
+ export {
283
+ handleProxyRequest
284
+ };
285
+ //# sourceMappingURL=core.mjs.map