Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-functions-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": patch
---

Align `browse functions` with the Browserbase Functions SDK by supporting optional project overrides, matching local invocation context and error payloads, and accepting the SDK's `--api-url` option.
4 changes: 4 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ browse functions invoke <function-id> --params '{"url":"https://example.com"}'
browse functions invoke --check-status <invocation-id>
```

Set `BROWSERBASE_API_KEY` for Functions commands. Browserbase infers the project
from the key; `--project-id` remains available as an explicit override, and
`--api-url` is accepted as an alias for `--base-url`.

## Templates

Discover and scaffold ready-to-run Browserbase example projects.
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/skills/browse/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ browse functions invoke <function-id> --params '{"url":"https://example.com"}'
browse functions invoke --check-status <invocation-id>
```

Functions commands use `BROWSERBASE_API_KEY`. Generated projects import `defineFn` from `@browserbasehq/sdk-functions`.
Functions commands use `BROWSERBASE_API_KEY`, and Browserbase infers the project from the key. Generated projects import `defineFn` from `@browserbasehq/sdk-functions`.

## Templates

Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/commands/functions/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export default class FunctionsDev extends BrowseCommand {
helpValue: "<apiKey>",
}),
"base-url": Flags.string({
aliases: ["api-url"],
char: "u",
description: "Override the Browserbase API base URL.",
helpValue: "<baseUrl>",
}),
Expand All @@ -37,6 +39,10 @@ export default class FunctionsDev extends BrowseCommand {
description: "Port to listen on.",
helpValue: "<port>",
}),
"project-id": Flags.string({
description: "Browserbase project ID used for local browser sessions.",
helpValue: "<projectId>",
}),
verbose: Flags.boolean({
description: "Print verbose runtime logs.",
}),
Expand All @@ -50,6 +56,7 @@ export default class FunctionsDev extends BrowseCommand {
entrypoint: args.entrypoint,
host: flags.host,
port: flags.port,
projectId: flags["project-id"],
verbose: flags.verbose ?? false,
});
}
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/functions/invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export default class FunctionsInvoke extends BrowseCommand {
helpValue: "<apiKey>",
}),
"base-url": Flags.string({
aliases: ["api-url"],
char: "u",
description: "Override the Browserbase API base URL.",
helpValue: "<baseUrl>",
}),
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/commands/functions/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ export default class FunctionsPublish extends BrowseCommand {
helpValue: "<apiKey>",
}),
"base-url": Flags.string({
aliases: ["api-url"],
char: "u",
description: "Override the Browserbase API base URL.",
helpValue: "<baseUrl>",
}),
"dry-run": Flags.boolean({
description: "Show what would be published without uploading.",
}),
"project-id": Flags.string({
description: "Browserbase project ID to publish into.",
helpValue: "<projectId>",
}),
};

async run(): Promise<void> {
Expand All @@ -39,6 +45,7 @@ export default class FunctionsPublish extends BrowseCommand {
baseUrl: flags["base-url"],
dryRun: flags["dry-run"] ?? false,
entrypoint: args.entrypoint,
projectId: flags["project-id"],
});
}
}
46 changes: 35 additions & 11 deletions packages/cli/src/lib/functions/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { fail } from "../errors.js";
import {
functionsRequest,
resolveEntrypoint,
resolveFunctionsApiConfig,
type FunctionsApiConfig,
resolveFunctionsProjectConfig,
type FunctionsProjectConfig,
} from "./shared.js";

const DEFAULT_RUNTIME_STARTUP_TIMEOUT_MS = 10_000;
Expand All @@ -22,10 +22,15 @@ export interface StartFunctionsDevServerOptions {
entrypoint: string;
host: string;
port: number;
projectId?: string;
verbose: boolean;
}

interface InvocationContext {
invocation: {
id: string;
region: "local";
};
session: {
id: string;
connectUrl: string;
Expand Down Expand Up @@ -105,7 +110,13 @@ class InvocationBridge {
sendJson(
this.invokeConnection.response,
500,
{ error: payload },
{
error: {
message: payload.errorMessage,
stackTrace: payload.stackTrace,
type: payload.errorType,
},
},
this.invokeConnection.corsHeaders,
);
try {
Expand Down Expand Up @@ -139,7 +150,7 @@ class InvocationBridge {
"content-type": "application/json",
"Lambda-Runtime-Aws-Request-Id": requestId,
"Lambda-Runtime-Deadline-Ms": String(Date.now() + 300_000),
"Lambda-Runtime-Invoked-Function-Arn": `arn:aws:lambda:local:function:${functionName}`,
"Lambda-Runtime-Invoked-Function-Arn": `arn:aws:lambda:us-east-1:000000000000:function:${functionName}`,
});
this.nextConnection.response.end(
JSON.stringify({
Expand Down Expand Up @@ -171,7 +182,7 @@ class InvocationBridge {
}

class BrowserSessionManager {
constructor(private readonly config: FunctionsApiConfig) {}
constructor(private readonly config: FunctionsProjectConfig) {}

async createSession(
sessionConfig: Record<string, unknown> = {},
Expand All @@ -181,7 +192,10 @@ class BrowserSessionManager {
headers: {
"content-type": "application/json",
},
body: JSON.stringify(sessionConfig),
body: JSON.stringify({
projectId: this.config.projectId,
...sessionConfig,
}),
});
const session = (await response.json()) as {
id?: string;
Expand All @@ -202,7 +216,10 @@ class BrowserSessionManager {
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ status: "REQUEST_RELEASE" }),
body: JSON.stringify({
projectId: this.config.projectId,
status: "REQUEST_RELEASE",
}),
});
}
}
Expand Down Expand Up @@ -330,7 +347,7 @@ export async function startFunctionsDevServer(
fail("Port must be an integer between 1 and 65535.");
}

const config = resolveFunctionsApiConfig(options);
const config = resolveFunctionsProjectConfig(options);
const runtimeApi = `${options.host}:${options.port}`;
const bridge = new InvocationBridge();
const sessionManager = new BrowserSessionManager(config);
Expand Down Expand Up @@ -538,7 +555,13 @@ async function routeRequest(
const accepted = bridge.triggerInvocation(
functionName,
params,
{ session },
{
invocation: {
id: randomUUID(),
region: "local",
},
session,
},
corsHeaders,
response,
);
Expand Down Expand Up @@ -606,11 +629,12 @@ async function routeRequest(
);
return;
}
const completed = await bridge.completeWithError(requestId, {
const runtimeError = {
errorMessage: payload?.errorMessage || "Unknown runtime error",
errorType: payload?.errorType || "RuntimeError",
stackTrace: Array.isArray(payload?.stackTrace) ? payload.stackTrace : [],
});
};
const completed = await bridge.completeWithError(requestId, runtimeError);
sendJson(
response,
completed ? 202 : 400,
Expand Down
14 changes: 10 additions & 4 deletions packages/cli/src/lib/functions/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function initFunctionsProject({
);
}

ensureCommand(packageManager);
const packageManagerVersion = ensureCommand(packageManager);

const projectRoot = resolve(projectName);
if (existsSync(projectRoot)) {
Expand All @@ -79,7 +79,9 @@ export async function initFunctionsProject({

const packageJson = {
name: projectName,
version: "1.0.0",
private: true,
packageManager: `${packageManager}@${packageManagerVersion}`,
type: "module",
scripts: {
dev: "browse functions dev index.ts",
Expand All @@ -98,7 +100,7 @@ export async function initFunctionsProject({

runPackageManager(
packageManager,
[...install, "@browserbasehq/sdk-functions", "playwright-core"],
[...install, "@browserbasehq/sdk-functions", "playwright-core", "zod"],
projectRoot,
);
runPackageManager(packageManager, [...installDev, "typescript", "@types/node"], projectRoot);
Expand Down Expand Up @@ -129,11 +131,15 @@ export async function initFunctionsProject({
);
}

function ensureCommand(command: string): void {
const result = spawnSync(command, ["--version"], { stdio: "ignore" });
function ensureCommand(command: string): string {
const result = spawnSync(command, ["--version"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
if (result.error || result.status !== 0) {
fail(`${command} is required but was not found on PATH.`);
}
return result.stdout.trim();
}

function runPackageManager(packageManager: "npm" | "pnpm", args: string[], cwd: string): void {
Expand Down
20 changes: 17 additions & 3 deletions packages/cli/src/lib/functions/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import {
functionsRequest,
pollUntil,
resolveEntrypoint,
resolveFunctionsApiConfig,
resolveFunctionsProjectConfig,
} from "./shared.js";

export interface PublishFunctionOptions {
apiKey?: string;
baseUrl?: string;
dryRun: boolean;
entrypoint: string;
projectId?: string;
}

interface BuildUploadResponse {
Expand Down Expand Up @@ -67,9 +68,11 @@ const defaultIgnorePatterns = [
".browserbase/",
];

const maxArchiveSizeBytes = 50 * 1024 * 1024;

export async function publishFunction(options: PublishFunctionOptions): Promise<void> {
const entrypoint = await resolveEntrypoint(options.entrypoint);
const config = resolveFunctionsApiConfig(options);
const config = resolveFunctionsProjectConfig(options);
const entrypointPath = relative(process.cwd(), entrypoint);

if (options.dryRun) {
Expand All @@ -82,6 +85,7 @@ export async function publishFunction(options: PublishFunctionOptions): Promise<
dryRun: true,
entrypoint: entrypointPath,
files: entries,
projectId: config.projectId,
},
null,
2,
Expand All @@ -92,8 +96,18 @@ export async function publishFunction(options: PublishFunctionOptions): Promise<

const { archivePath } = await createArchive(process.cwd());
try {
const archiveStats = await stat(archivePath);
if (archiveStats.size > maxArchiveSizeBytes) {
fail(
`Functions archive is ${(archiveStats.size / 1024 / 1024).toFixed(2)} MB; the maximum is 50 MB. Add files to .gitignore to reduce its size.`,
);
}

const formData = new FormData();
formData.append("metadata", JSON.stringify({ entrypoint: entrypointPath }));
formData.append(
"metadata",
JSON.stringify({ entrypoint: entrypointPath, projectId: config.projectId }),
);
formData.append(
"archive",
new Blob([await readFile(archivePath)], { type: "application/gzip" }),
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/lib/functions/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
baseUrl: string;
}

export interface FunctionsProjectConfig extends FunctionsApiConfig {
projectId?: string;
}

export interface PollOptions<T> {
done: (value: T) => boolean;
intervalMs?: number;
Expand All @@ -32,6 +36,16 @@
};
}

export function resolveFunctionsProjectConfig(args: {
apiKey?: string;
baseUrl?: string;
projectId?: string;
}): FunctionsProjectConfig {
const apiConfig = resolveFunctionsApiConfig(args);
const projectId = args.projectId ?? process.env.BROWSERBASE_PROJECT_ID;
return projectId ? { ...apiConfig, projectId } : apiConfig;
}

export async function functionsRequest(
config: FunctionsApiConfig,
path: string,
Expand All @@ -43,7 +57,7 @@
...init,
headers: {
"x-bb-api-key": config.apiKey,
...(init.headers ?? {}),

Check warning on line 60 in packages/cli/src/lib/functions/shared.ts

View workflow job for this annotation

GitHub Actions / Check

unicorn(no-useless-fallback-in-spread)

Empty fallbacks in spreads are unnecessary
},
});
} catch (error) {
Expand Down
Loading
Loading