Skip to content
Merged
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
4 changes: 2 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const TEMPLATES: Record<TemplateKey, TemplateInfo> = {
},
[TEMPLATE_STAGEHAND]: {
name: "Stagehand",
description: "Implements the Stagehand SDK",
description: "Implements the Stagehand v3 SDK",
languages: [LANGUAGE_TYPESCRIPT],
},
[TEMPLATE_ADVANCED_SAMPLE]: {
Expand Down Expand Up @@ -103,7 +103,7 @@ const INVOKE_SAMPLES: Record<
[TEMPLATE_SAMPLE_APP]:
'kernel invoke ts-basic get-page-title --payload \'{"url": "https://www.google.com"}\'',
[TEMPLATE_STAGEHAND]:
'kernel invoke ts-stagehand stagehand-task --payload \'{"query": "Best wired earbuds"}\'',
'kernel invoke ts-stagehand teamsize-task --payload \'{"company": "Kernel"}\'',
[TEMPLATE_ADVANCED_SAMPLE]: "kernel invoke ts-advanced test-captcha-solver",
[TEMPLATE_COMPUTER_USE]:
'kernel invoke ts-cu cu-task --payload \'{"query": "Return the first url of a search result for NYC restaurant reviews Pete Wells"}\'',
Expand Down
51 changes: 48 additions & 3 deletions templates/typescript/stagehand/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Kernel Typscript Sample App - Stagehand
# Kernel TypeScript Sample App - Stagehand

This is a simple Kernel application that implements the Stagehand SDK.
A Stagehand-powered browser automation app that extracts team size information from Y Combinator company pages.

See the [docs](https://onkernel.com/docs/quickstart) for information.
## What it does

The `teamsize-task` searches for a startup on Y Combinator's company directory and extracts the team size (number of employees).

## Input

```json
{
"company": "kernel" // Startup name to search (optional, defaults to "kernel")
}
```

## Output

```json
{
"teamSize": "11" // Team size as shown on YC company page
}
```

## Setup

Create a `.env` file:

```
OPENAI_API_KEY=your-openai-api-key
```

## Deploy

```bash
kernel login
kernel deploy index.ts --env-file .env
```

## Invoke

Default query (searches for "kernel"):
```bash
kernel invoke ts-stagehand teamsize-task
```

Custom query:
```bash
kernel invoke ts-stagehand teamsize-task --payload '{"company": "Mixpanel"}'
```
69 changes: 37 additions & 32 deletions templates/typescript/stagehand/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,72 +6,77 @@ const kernel = new Kernel();

const app = kernel.app('ts-stagehand');

interface SearchQueryInput {
query: string;
interface CompanyInput {
company: string;
}

interface SearchQueryOutput {
url: string;
interface TeamSizeOutput {
teamSize: string;
}

// LLM API Keys are set in the environment during `kernel deploy <filename> -e OPENAI_API_KEY=XXX`
// See https://onkernel.com/docs/launch/deploy#environment-variables
// See https://www.onkernel.com/docs/apps/deploy#environment-variables

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

if (!OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY is not set');
}

app.action<SearchQueryInput, SearchQueryOutput>(
'stagehand-task',
async (ctx: KernelContext, payload?: SearchQueryInput): Promise<SearchQueryOutput> => {
// A function that returns the first search result of a given search query from Google
app.action<CompanyInput, TeamSizeOutput>(
'teamsize-task',
async (ctx: KernelContext, payload?: CompanyInput): Promise<TeamSizeOutput> => {
// A function that returns the team size of a Y Combinator startup

// Args:
// ctx: Kernel context containing invocation information
// payload: A search query string
// payload: A startup name to search for on YCombinator's website

// Returns:
// output: The URL of the first search result
// output: The team size (number of employees) of the startup

if (!payload?.query) {
throw new Error('Query is required');
}
const company = payload?.company || 'kernel';

const kernelBrowser = await kernel.browsers.create({
invocation_id: ctx.invocation_id,
stealth: true,
});

console.log("Kernel browser live view url: ", kernelBrowser.browser_live_view_url);

const stagehand = new Stagehand({
env: "LOCAL",
verbose: 1,
domSettleTimeoutMs: 30_000,
modelName: "openai/gpt-4o",
modelClientOptions: {
apiKey: OPENAI_API_KEY,
},
localBrowserLaunchOptions: {
cdpUrl: kernelBrowser.cdp_ws_url,
}
},
model: "openai/gpt-4.1",
apiKey: OPENAI_API_KEY,
verbose: 1,
domSettleTimeout: 30_000
});
await stagehand.init();

/////////////////////////////////////
// Your Stagehand implementation here
/////////////////////////////////////
const page = stagehand.page;
await page.act(`Type in ${payload.query} into the search bar`);
await page.act("Click the search button");
const output = await page.extract({
instruction: "Extract the url of the first search result",
schema: z.object({ url: z.string() })
});
const page = stagehand.context.pages()[0];
await page.goto("https://www.ycombinator.com/companies");

await stagehand.act(`Type in ${company} into the search box`);
await stagehand.act("Click on the first search result");

// Schema definition
const teamSizeSchema = z.object({
teamSize: z.string(),
});
// Extract team size from the YC startup page
const output = await stagehand.extract(
"Extract the team size (number of employees) shown on this Y Combinator company page.",
teamSizeSchema
);
await stagehand.close();

await kernel.browsers.deleteByID(kernelBrowser.session_id);

return output;
},
);
Loading