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
465 changes: 287 additions & 178 deletions index.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion templates/python/browser-use/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"browser-use>=0.1.46",
"kernel==0.2.0",
"kernel==0.3.0",
"langchain-openai>=0.3.11",
"pydantic>=2.10.6",
]
66 changes: 66 additions & 0 deletions templates/python/persistent-browser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual Environment
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
activate/

# IDE
.idea/
.vscode/
*.swp
*.swo
.project
.pydevproject
.settings/

# Testing
.coverage
htmlcov/
.pytest_cache/
.tox/
.nox/
coverage.xml
*.cover
.hypothesis/

# Logs
*.log
logs/

# OS
.DS_Store
Thumbs.db

# Misc
.cache/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.temp/
.tmp/
5 changes: 5 additions & 0 deletions templates/python/persistent-browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Kernel Python Persistent Browser Example

This is a simple Kernel application that shows how to use a persistent browser.

The action reuses the same browser across invocations of the action, which lets it optimize for the case where the browser is already open and at the page of interest.
73 changes: 73 additions & 0 deletions templates/python/persistent-browser/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import kernel
from kernel import Kernel
from playwright.async_api import async_playwright
from typing import TypedDict
from urllib.parse import urlparse
from datetime import datetime

client = Kernel()

# Create a new Kernel app
app = kernel.App("python-persistent-browser")

class PageTitleInput(TypedDict):
url: str

class PageTitleOutput(TypedDict):
title: str
elapsed_ms: float

@app.action("get-page-title")
async def get_page_title(ctx: kernel.KernelContext, input_data: PageTitleInput) -> PageTitleOutput:
"""
A function that extracts the title of a webpage

Args:
ctx: Kernel context containing invocation information
input_data: An object with a URL property

Returns:
A dictionary containing the page title
"""
url = input_data.get("url")
if not url or not isinstance(url, str):
raise ValueError("URL is required and must be a string")

# Add https:// if no protocol is present
if not url.startswith(('http://', 'https://')):
url = f"https://{url}"

# Validate the URL
try:
urlparse(url)
except Exception:
raise ValueError(f"Invalid URL: {url}")

# Create a browser instance using the context's invocation_id and a persistent id
kernel_browser = client.browsers.create(
invocation_id=ctx.invocation_id,
persistence={"id": "my-awesome-persistent-browser-2"}
)
print("Kernel browser live view url: ", kernel_browser.browser_live_view_url)

async with async_playwright() as playwright:
browser = await playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url)
try:
now = datetime.now()
context = len(browser.contexts) > 0 and browser.contexts[0] or await browser.new_context()
page = len(context.pages) > 0 and context.pages[0] or await context.new_page()
current_url = page.url
print("Current url: ", current_url)
if current_url != url:
print("Not at url, navigating to it")
await page.goto(url)
else:
print("Already at url, skipping navigation")
# for some reason going straight to page.title() leads to an error: Page.title: Execution context was destroyed, most likely because of a navigation
# calling bring_to_front() seems to fix it :shrug:
await page.bring_to_front()
title = await page.title()
elapsedMilliseconds = (datetime.now() - now).total_seconds() * 1000
return {"title": title, "elapsed_ms": elapsedMilliseconds}
finally:
await browser.close()
10 changes: 10 additions & 0 deletions templates/python/persistent-browser/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "persistent-browser"
version = "0.1.0"
description = "Basic Kernel application that demonstrates the use of persistent browsers"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["kernel==0.3.0", "playwright>=1.52.0"]

[dependency-groups]
dev = ["mypy>=1.15.0"]
354 changes: 354 additions & 0 deletions templates/python/persistent-browser/uv.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion templates/python/sample-app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
description = "Kernel application template - Python"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["kernel==0.2.0", "playwright>=1.52.0"]
dependencies = ["kernel==0.3.0", "playwright>=1.52.0"]

[dependency-groups]
dev = ["mypy>=1.15.0"]
39 changes: 39 additions & 0 deletions templates/typescript/persistent-browser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Dependencies
node_modules/
package-lock.json

# TypeScript
*.tsbuildinfo
dist/
build/

# Environment
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Testing
coverage/
.nyc_output/

# Misc
.cache/
.temp/
.tmp/
5 changes: 5 additions & 0 deletions templates/typescript/persistent-browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Kernel Typscript Persistent Browser Example

This is a simple Kernel application that extracts the title from a webpage, but uses a persistent browser to avoid navigations on subsequent invocations.

See the [docs](https://docs.onkernel.com/quickstart) for information.
80 changes: 80 additions & 0 deletions templates/typescript/persistent-browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Kernel, type KernelContext } from "@onkernel/sdk";
import { chromium, type BrowserContext, type Page } from "playwright";

const kernel = new Kernel();

const app = kernel.app("ts-persistent-browser");

interface PageTitleInput {
url: string;
}

interface PageTitleOutput {
title: string;
elapsed_ms: number;
}

app.action<PageTitleInput, PageTitleOutput>(
"get-page-title",
async (
ctx: KernelContext,
payload?: PageTitleInput
): Promise<PageTitleOutput> => {
// A function that extracts the title of a webpage

// Args:
// ctx: Kernel context containing invocation information
// payload: An object with a URL property

// Returns:
// A dictionary containing the page title
if (!payload?.url) {
throw new Error("URL is required");
}

if (
!payload.url.startsWith("http://") &&
!payload.url.startsWith("https://")
) {
payload.url = `https://${payload.url}`;
}

// Validate the URL
try {
new URL(payload.url);
} catch {
throw new Error(`Invalid URL: ${payload.url}`);
}

const kernelBrowser = await kernel.browsers.create({
invocation_id: ctx.invocation_id,
persistence: {
id: "ts-persistent-browser",
},
});

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

const browser = await chromium.connectOverCDP(kernelBrowser.cdp_ws_url);
try {
const now = Date.now();
const contexts = await browser.contexts();
let context: BrowserContext = contexts[0] ?? (await browser.newContext());
let page: Page = context.pages()[0] ?? (await context.newPage());
if (page.url() !== payload.url) {
console.log("navigating to", payload.url);
await page.goto(payload.url);
} else {
console.log("page already loaded, skipping navigation");
await page.bringToFront();
}
const title = await page.title();
return { title, elapsed_ms: Date.now() - now };
} finally {
await browser.close();
}
}
);
14 changes: 14 additions & 0 deletions templates/typescript/persistent-browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "ts-basic",
"module": "index.ts",
"type": "module",
"private": true,
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"@onkernel/sdk": "0.3.0",
"playwright": "^1.52.0"
}
}

61 changes: 61 additions & 0 deletions templates/typescript/persistent-browser/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading