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 @@ -96,7 +96,7 @@ const INVOKE_SAMPLES: Record<
[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"}\'',
[TEMPLATE_CUA]:
'kernel invoke ts-cua cua-task --payload \'{"query": "Go to https://news.ycombinator.com and get the top 5 articles"}\'',
'kernel invoke ts-cua cua-task --payload \'{"task": "Go to https://news.ycombinator.com and get the top 5 articles"}\'',
},
[LANGUAGE_PYTHON]: {
[TEMPLATE_SAMPLE_APP]:
Expand All @@ -108,7 +108,7 @@ const INVOKE_SAMPLES: Record<
[TEMPLATE_COMPUTER_USE]:
'kernel invoke python-cu cu-task --payload \'{"query": "Return the first url of a search result for NYC restaurant reviews Pete Wells"}\'',
[TEMPLATE_CUA]:
'kernel invoke python-cua cua-task --payload \'{"query": "Go to https://news.ycombinator.com and get the top 5 articles"}\'',
'kernel invoke python-cua cua-task --payload \'{"task": "Go to https://news.ycombinator.com and get the top 5 articles"}\'',
},
};

Expand Down
33 changes: 17 additions & 16 deletions templates/python/cua/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"annotated-types==0.7.0",
"anyio==4.8.0",
"certifi==2025.1.31",
"charset-normalizer==3.4.1",
"anyio==4.9.0",
"certifi==2025.6.15",
"charset-normalizer==3.4.2",
"distro==1.9.0",
"greenlet==3.1.1",
"h11==0.14.0",
"httpcore==1.0.7",
"greenlet==3.2.3",
"h11==0.16.0",
"httpcore==1.0.9",
"httpx==0.28.1",
"idna==3.10",
"jiter==0.8.2",
"pillow==11.1.0",
"playwright==1.50.0",
"pydantic==2.10.6",
"pydantic_core==2.27.2",
"pyee==12.1.1",
"python-dotenv==1.0.1",
"requests==2.32.3",
"jiter==0.10.0",
"pillow==11.2.1",
"kernel>=0.6.0",
"playwright==1.52.0",
"pydantic==2.11.7",
"pydantic_core==2.35.1",
"pyee==13.0.0",
"python-dotenv==1.1.0",
"requests==2.32.4",
"sniffio==1.3.1",
"typing_extensions==4.12.2",
"urllib3==2.3.0",
"typing_extensions==4.14.0",
"urllib3==2.5.0",
]
2 changes: 2 additions & 0 deletions templates/typescript/cua/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
# KERNEL_API_KEY=YOUR_KERNEL_KEY
2 changes: 1 addition & 1 deletion templates/typescript/cua/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
}
2 changes: 1 addition & 1 deletion templates/typescript/cua/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ This is a Kernel application that demonstrates using the Computer Using Agent (C
It generally follows the [OpenAI CUA Sample App Reference](https://github.com/openai/openai-cua-sample-app) and uses Playwright via Kernel for browser automation.
Also makes use of the latest OpenAI SDK format, and has local equivalent to Kernel methods for local testing before deploying on Kernel.

See the [docs](https://docs.onkernel.com/quickstart) for information.
See the [docs](https://docs.onkernel.com/quickstart) for information.
166 changes: 81 additions & 85 deletions templates/typescript/cua/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import "dotenv/config";
import { Kernel, type KernelContext } from "@onkernel/sdk";
import { Agent } from "./lib/agent";
import computers from "./lib/computers";
import 'dotenv/config';
import { Kernel, type KernelContext } from '@onkernel/sdk';
import { Agent } from './lib/agent';
import computers from './lib/computers';
import type { ResponseOutputMessage, ResponseItem } from 'openai/resources/responses/responses';

interface CuaInput {
task: string;
}
interface CuaOutput {
elapsed: number;
answer: string | null;
logs?: ResponseItem[];
}

const kernel = new Kernel();
const app = kernel.app("ts-cua");
const app = kernel.app('ts-cua');

// LLM API Keys are set in the environment during `kernel deploy <filename> -e ANTHROPIC_API_KEY=XXX`
// See https://docs.onkernel.com/launch/deploy#environment-variables
if (!process.env.OPENAI_API_KEY) throw new Error('OPENAI_API_KEY is not set');
if (!process.env.OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY is not set');
}

/**
* Example app that run an agent using openai CUA
Expand All @@ -24,88 +34,74 @@ if (!process.env.OPENAI_API_KEY) throw new Error('OPENAI_API_KEY is not set');
* kernel logs ts-cua -f # Open in separate tab
*/

interface CuaInput {
task: string;
}

interface CuaOutput {
elapsed: number;
response?: Array<object>;
answer: object;
}

app.action<CuaInput, CuaOutput>(
"cua-task",
async (ctx: KernelContext, payload?: CuaInput): Promise<CuaOutput> => {
const startTime = Date.now();
const kernelBrowser = await kernel.browsers.create({
invocation_id: ctx.invocation_id,
});
console.log(
"> Kernel browser live view url: ",
kernelBrowser.browser_live_view_url,
);

if (!payload?.task){
throw new Error('task is required');
}

try {
'cua-task',
async (ctx: KernelContext, payload?: CuaInput): Promise<CuaOutput> => {
const start = Date.now();
if (!payload?.task) throw new Error('task is required');

// kernel browser
const { computer } = await computers.create({
type: "kernel", // for local testing before deploying to Kernel, you can use type: "local"
cdp_ws_url: kernelBrowser.cdp_ws_url,
});
try {
const kb = await kernel.browsers.create({ invocation_id: ctx.invocation_id });
console.log('> Kernel browser live view url:', kb.browser_live_view_url);

// setup agent
const agent = new Agent({
model: "computer-use-preview",
computer,
tools: [], // additional function_call tools to provide to the llm
acknowledge_safety_check_callback: (message: string) => {
console.log(`> safety check: ${message}`);
return true; // Auto-acknowledge all safety checks for testing
},
});
const { computer } = await computers.create({ type: 'kernel', cdp_ws_url: kb.cdp_ws_url });
const agent = new Agent({
model: 'computer-use-preview',
computer,
tools: [],
acknowledge_safety_check_callback: (m: string): boolean => {
console.log(`> safety check: ${m}`);
return true;
},
});

// start agent run
const response = await agent.runFullTurn({
messages: [
{
role: "system",
content: `- Current date and time: ${new Date().toISOString()} (${new Date().toLocaleDateString("en-US", { weekday: "long" })})`,
},
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: payload.task,
// text: "go to https://news.ycombinator.com , open top article , describe the target website design (in yaml format)"
},
],
},
],
print_steps: true, // log function_call and computer_call actions
debug: true, // show agent debug logs (llm messages and responses)
show_images: false, // if set to true, response messages stack will return base64 images (webp format) of screenshots, if false, replaced with "[omitted]""
});
// run agent and get response
const logs = await agent.runFullTurn({
messages: [
{
role: 'system',
content: `- Current date and time: ${new Date().toISOString()} (${new Date().toLocaleDateString(
'en-US',
{ weekday: 'long' },
)})`,
},
{
type: 'message',
role: 'user',
content: [{ type: 'input_text', text: payload.task }],
},
],
print_steps: true,
debug: true,
show_images: false,
});

console.log("> agent run done");
const elapsed = parseFloat(((Date.now() - start) / 1000).toFixed(2));

const endTime = Date.now();
const timeElapsed = (endTime - startTime) / 1000; // Convert to seconds
// filter only LLM messages
const messages = logs.filter(
(item): item is ResponseOutputMessage =>
item.type === 'message' &&
typeof (item as ResponseOutputMessage).role === 'string' &&
Array.isArray((item as ResponseOutputMessage).content),
);
const assistant = messages.find((m) => m.role === 'assistant');
const lastContentIndex = assistant?.content?.length ? assistant.content.length - 1 : -1;
const lastContent = lastContentIndex >= 0 ? assistant?.content?.[lastContentIndex] : null;
const answer = lastContent && 'text' in lastContent ? lastContent.text : null;

return {
// response, // full messages stack trace
elapsed: parseFloat(timeElapsed.toFixed(2)),
answer: response?.slice(-1)?.[0]?.content?.[0]?.text ?? null,
};
} finally {
// Note: KernelPlaywrightComputer handles browser cleanup internally
// No need to manually close browser here
}
},
return {
// logs, // optionally, get the full agent run messages logs
elapsed,
answer,
};
} catch (error) {
const elapsed = parseFloat(((Date.now() - start) / 1000).toFixed(2));
console.error('Error in cua-task:', error);
return {
elapsed,
answer: null,
};
}
},
);
Loading