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
31 changes: 11 additions & 20 deletions packages/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ Both classes mirror pi constructor shapes and behavior, with minimal additions:
- `client` (Kernel SDK client)
- CUA model refs (`"provider:model"`) accepted where pi expects a concrete model
- `extraTools` to add your own pi tools alongside the built-in browser tools
- `batchTool: true` to let the model run multiple browser actions in one tool call
- `computerUseExtra: true` to let the model use a small navigation helper

If auth callbacks are omitted, both classes default to CUA env var conventions:
Expand All @@ -97,27 +96,20 @@ If auth callbacks are omitted, both classes default to CUA env var conventions:

### Tool Defaults

By default, the classes install provider-selected canonical CUA computer tool
executors using runtime specs from `@onkernel/cua-ai`. Use `extraTools` to add
your own pi tools alongside the provider's computer-use tools. This is useful
when the model needs to call application-specific code, such as looking up a
record, writing a database row, or handing off to another service while it also
controls the browser.
By default, the classes install provider-selected CUA computer tool executors
from `@onkernel/cua-ai`. Each provider decides which tool names the model sees;
the matching executor adapter translates returned tool calls into canonical CUA
actions that run against the Kernel browser.

`batchTool: true` adds the `batch_computer_actions` tool. Use it when you want
the model to group several browser actions into one call, for example moving,
clicking, typing, waiting, and then reading a screenshot. The batch tool is
synthesized from the selected provider's normal browser action definitions, so
it only batches actions that provider runtime already supports.
Use `extraTools` to add your own pi tools alongside the provider's
computer-use tools. This is useful when the model needs to call
application-specific code, such as looking up a record, writing a database row,
or handing off to another service while it also controls the browser.

`computerUseExtra: true` adds the `computer_use_extra` tool. Use it when you
want one compact helper for common browser navigation/read operations:
`goto`, `back`, `forward`, and `url`.

The TypeScript API follows pi's camelCase option style (`extraTools`,
`batchTool`, `computerUseExtra`). Names like `batch_computer_actions` and
`computer_use_extra` are the literal tool names the model may see in traces.

### Model Switching

`CuaAgent` follows pi `Agent` semantics: assign `agent.state.model` to a
Expand All @@ -131,8 +123,8 @@ the next provider request.

### Tool Composition

Use `createCuaComputerTools()` to compose your own tool list from canonical
tool definitions:
Use `createCuaComputerTools()` to compose your own tool list from provider
execution adapters:

```ts
import { resolveCuaRuntimeSpec } from "@onkernel/cua-ai";
Expand All @@ -143,8 +135,7 @@ const tools = [
...createCuaComputerTools({
browser,
client,
toolDefinitions: runtime.toolDefinitions,
batchTool: true,
toolExecutors: runtime.toolExecutors,
}),
myCustomTool,
];
Expand Down
14 changes: 1 addition & 13 deletions packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from "./vendor/pi-agent-core/index";
import {
type Api,
CUA_BATCH_TOOL_NAME,
CUA_NAVIGATION_TOOL_NAME,
type CuaModelRef,
getCuaEnvApiKey,
Expand Down Expand Up @@ -65,8 +64,6 @@ export type CuaAgentOptions = Omit<AgentOptions, "initialState"> & {
initialState: CuaAgentInitialState;
/** Add your own pi tools alongside the built-in browser tools. */
extraTools?: AgentTool[];
/** Expose a batch tool so the model can run multiple browser actions in one call. */
batchTool?: boolean;
/** Expose a helper for browser navigation and URL reads. */
computerUseExtra?: boolean;
};
Expand All @@ -90,8 +87,6 @@ export type CuaAgentHarnessOptions<
model: CuaRuntimeInput;
/** Add your own pi tools alongside the built-in browser tools. */
extraTools?: AgentTool[];
/** Expose a batch tool so the model can run multiple browser actions in one call. */
batchTool?: boolean;
/** Expose a helper for browser navigation and URL reads. */
computerUseExtra?: boolean;
/** Optional payload hook composed after the provider-specific CUA payload hook. */
Expand All @@ -115,7 +110,6 @@ class CuaRuntimeController {
client: Kernel;
model: CuaRuntimeInput;
extraTools?: AgentTool[];
batchTool?: boolean;
computerUseExtra?: boolean;
systemPrompt?: unknown;
onPayload?: SimpleStreamOptions["onPayload"];
Expand Down Expand Up @@ -149,10 +143,9 @@ class CuaRuntimeController {
...createCuaComputerTools({
browser: this.options.browser,
client: this.options.client,
toolDefinitions: this.runtimeSpec.toolDefinitions,
toolExecutors: this.runtimeSpec.toolExecutors,
coordinateSystem: this.runtimeSpec.coordinateSystem,
screenshot: this.runtimeSpec.screenshot,
batchTool: this.options.batchTool,
computerUseExtra: this.options.computerUseExtra,
}),
...(this.options.extraTools ?? []),
Expand All @@ -170,7 +163,6 @@ class CuaRuntimeController {
keepToolNames(): string[] {
return [
...(this.options.extraTools ?? []).map((tool) => tool.name),
...(this.options.batchTool ? [CUA_BATCH_TOOL_NAME] : []),
...(this.options.computerUseExtra ? [CUA_NAVIGATION_TOOL_NAME] : []),
];
}
Expand Down Expand Up @@ -242,7 +234,6 @@ export class CuaAgent extends Agent {
streamFn,
prepareNextTurn,
extraTools,
batchTool,
computerUseExtra,
...agentOptions
} = options;
Expand All @@ -251,7 +242,6 @@ export class CuaAgent extends Agent {
client,
model: initialState.model,
extraTools,
batchTool,
computerUseExtra,
systemPrompt: initialState.systemPrompt,
onPayload,
Expand Down Expand Up @@ -362,7 +352,6 @@ export class CuaAgentHarness<
client,
model,
extraTools,
batchTool,
computerUseExtra,
systemPrompt,
getApiKeyAndHeaders,
Expand All @@ -375,7 +364,6 @@ export class CuaAgentHarness<
client,
model,
extraTools,
batchTool,
computerUseExtra,
systemPrompt,
onPayload,
Expand Down
2 changes: 0 additions & 2 deletions packages/agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export type {
ComputerToolOptions,
CuaExecutorTool,
NavigationDetails,
SupportedCuaExecutorToolName,
} from "./tools";
export { SUPPORTED_CUA_EXECUTOR_TOOL_NAMES } from "./tools";
export { CuaAgent, CuaAgentHarness } from "./agent";
export type { CuaAgentHarnessOptions, CuaAgentOptions, CuaAgentState } from "./agent";
105 changes: 31 additions & 74 deletions packages/agent/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,26 @@ import type Kernel from "@onkernel/sdk";
import type { ImageContent, TextContent, Tool } from "@earendil-works/pi-ai";
import type { TSchema } from "typebox";
import {
CUA_ACTION_TYPES,
CUA_BATCH_TOOL_NAME,
CUA_NAVIGATION_TOOL_NAME,
createCuaBatchToolDefinition,
createCuaNavigationToolDefinition,
type ComputerToolCoordinateSystem,
type CuaAction,
type CuaActionType,
type CuaBatchInput,
type CuaNavigationInput,
type CuaScreenshotSpec,
type CuaToolExecutorSpec,
} from "@onkernel/cua-ai";
import { InternalComputerTranslator, type KernelBrowser } from "./translator/translator";
import type { AgentTool, AgentToolResult } from "./vendor/pi-agent-core/index";

export interface ComputerToolOptions {
browser: KernelBrowser;
client: Kernel;
toolDefinitions: Tool[];
toolExecutors: CuaToolExecutorSpec[];
coordinateSystem?: ComputerToolCoordinateSystem;
screenshot?: CuaScreenshotSpec;
batchTool?: boolean;
computerUseExtra?: boolean;
}

const CUA_ACTION_TOOL_NAMES = new Set<string>(CUA_ACTION_TYPES);
export const SUPPORTED_CUA_EXECUTOR_TOOL_NAMES = [
CUA_BATCH_TOOL_NAME,
CUA_NAVIGATION_TOOL_NAME,
...CUA_ACTION_TYPES,
] as const;
export type SupportedCuaExecutorToolName = (typeof SUPPORTED_CUA_EXECUTOR_TOOL_NAMES)[number];

type ToolContent = Array<TextContent | ImageContent>;

export interface BatchDetails {
Expand All @@ -54,43 +41,27 @@ type BatchTool = AgentTool<TSchema, BatchDetails>;
type NavigationTool = AgentTool<TSchema, NavigationDetails>;
type ActionTool = AgentTool<TSchema, BatchDetails>;
export type CuaExecutorTool = BatchTool | NavigationTool | ActionTool;
type NavigationExecutorSpec = { kind: "navigation"; definition: Tool };
type ComputerExecutorSpec = CuaToolExecutorSpec | NavigationExecutorSpec;

export function createCuaComputerTools(args: ComputerToolOptions): CuaExecutorTool[] {
const translator = new InternalComputerTranslator(args);
return withSynthesizedTools(args).map((definition) => createExecutorTool(definition, translator));
return withNavigationTool(args).map((executor) => createExecutorTool(executor, translator));
}

function withSynthesizedTools(args: ComputerToolOptions): Tool[] {
const definitions = [...args.toolDefinitions];
const existing = new Set(definitions.map((definition) => definition.name));
const actionTypes = definitions
.map((definition) => definition.name)
.filter((name): name is CuaActionType => CUA_ACTION_TOOL_NAMES.has(name));
if (args.batchTool && actionTypes.length > 0 && !existing.has(CUA_BATCH_TOOL_NAME)) {
definitions.push(createCuaBatchToolDefinition(actionTypes));
}
function withNavigationTool(args: ComputerToolOptions): ComputerExecutorSpec[] {
const executors: ComputerExecutorSpec[] = [...args.toolExecutors];
const existing = new Set(executors.map((executor) => executor.definition.name));
if (args.computerUseExtra && !existing.has(CUA_NAVIGATION_TOOL_NAME)) {
definitions.push(createCuaNavigationToolDefinition());
const definition = createCuaNavigationToolDefinition();
executors.push({ kind: "navigation", definition });
}
return definitions;
return executors;
}

function createExecutorTool(definition: Tool, translator: InternalComputerTranslator): CuaExecutorTool {
if (definition.name === CUA_BATCH_TOOL_NAME) {
const tool: BatchTool = {
name: definition.name,
label: definition.name,
description: definition.description,
parameters: definition.parameters,
async execute(_toolCallId: string, params: unknown): Promise<AgentToolResult<BatchDetails>> {
const result = await executeBatchTool(translator, asBatchInput(params));
if (result.isError) throw Object.assign(new Error(result.details.statusText), result);
return { content: result.content, details: result.details };
},
};
return tool;
}
if (definition.name === CUA_NAVIGATION_TOOL_NAME) {
function createExecutorTool(executor: ComputerExecutorSpec, translator: InternalComputerTranslator): CuaExecutorTool {
const { definition } = executor;
if (isNavigationExecutor(executor)) {
const tool: NavigationTool = {
name: definition.name,
label: definition.name,
Expand All @@ -104,26 +75,23 @@ function createExecutorTool(definition: Tool, translator: InternalComputerTransl
};
return tool;
}
if (CUA_ACTION_TOOL_NAMES.has(definition.name)) {
const actionType = definition.name as CuaActionType;
const tool: ActionTool = {
name: definition.name,
label: definition.name,
description: definition.description,
parameters: definition.parameters,
executionMode: "sequential",
async execute(_toolCallId: string, params: unknown): Promise<AgentToolResult<BatchDetails>> {
const action = { ...(params && typeof params === "object" ? params : {}), type: actionType } as CuaAction;
const result = await executeBatchTool(translator, { actions: [action] });
if (result.isError) throw Object.assign(new Error(result.details.statusText), result);
return { content: result.content, details: result.details };
},
};
return tool;
}
throw new Error(
`unsupported CUA computer tool definition: ${definition.name}`,
);
const tool: ActionTool = {
name: definition.name,
label: definition.name,
description: definition.description,
parameters: definition.parameters,
executionMode: "sequential",
async execute(_toolCallId: string, params: unknown): Promise<AgentToolResult<BatchDetails>> {
const result = await executeBatchTool(translator, { actions: executor.toActions(params) });
if (result.isError) throw Object.assign(new Error(result.details.statusText), result);
return { content: result.content, details: result.details };
},
};
return tool;
}

function isNavigationExecutor(executor: ComputerExecutorSpec): executor is NavigationExecutorSpec {
return "kind" in executor && executor.kind === "navigation";
}

async function executeBatchTool(translator: InternalComputerTranslator, params: CuaBatchInput): Promise<{
Expand Down Expand Up @@ -190,17 +158,6 @@ async function executeNavigationTool(translator: InternalComputerTranslator, par
return { content, details: { action, statusText, ...(url ? { url } : {}), ...(error ? { error: error.message } : {}) }, isError: Boolean(error) };
}

function asBatchInput(value: unknown): CuaBatchInput {
if (
value &&
typeof value === "object" &&
Array.isArray((value as { actions?: unknown }).actions)
) {
return value as CuaBatchInput;
}
throw new Error("invalid batch_computer_actions parameters");
}

function asNavigationInput(value: unknown): CuaNavigationInput {
if (
value &&
Expand Down
Loading
Loading