Skip to content

Commit 8d9ad6f

Browse files
committed
Add CUA AgentHarness model switching
1 parent 7df5044 commit 8d9ad6f

39 files changed

Lines changed: 7633 additions & 139 deletions

‎docs/architecture.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ someone who wants to read the code, contribute, or fork.
4141
- provider payload transforms and protocol quirks (for example, Yutori tool serialization policy)
4242
- canonical CUA tool-definition exports
4343
- `@onkernel/cua-agent` owns browser execution orchestration:
44-
- `CuaAgent` / `CuaHarness` class wiring around `pi-agent-core`
44+
- `CuaAgent` / `CuaAgentHarness` class wiring around vendored pi agent core
4545
- executing canonical CUA tool calls against Kernel browsers
4646
- typed executor coverage and translator integration
4747

@@ -66,7 +66,7 @@ Dev/test:
6666
└── @onkernel/ptywright (PTY-backed TUI regression harness)
6767
6868
External:
69-
├── @earendil-works/pi-agent-core # Agent loop, tool execution, streaming, steering
69+
├── vendored pi agent core # Agent loop, tool execution, streaming, steering
7070
│ └── @earendil-works/pi-ai # Provider transport (OpenAI Responses, Anthropic Messages, Google GenAI)
7171
├── @earendil-works/pi-coding-agent # bash / read / write / edit / grep / find / ls AgentTools + SessionManager + skills
7272
├── @earendil-works/pi-tui # Terminal, Editor, Image, differential renderer

‎package-lock.json‎

Lines changed: 2 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/agent/README.md‎

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# `@onkernel/cua-agent`
22

3-
Kernel browser computer-use classes built on
4-
[`@earendil-works/pi-agent-core`](https://github.com/earendil-works/pi/tree/main/packages/agent).
3+
Kernel browser computer-use classes built on vendored pi `Agent` and
4+
`AgentHarness` source.
55

6-
This package keeps pi-agent-core semantics intact and adds browser execution
6+
This package keeps pi agent semantics intact and adds browser execution
77
plumbing for canonical CUA tools.
88

99
## Installation
@@ -33,12 +33,12 @@ const agent = new CuaAgent({
3333
await agent.prompt("Open news.ycombinator.com and summarize the top story.");
3434
```
3535

36-
## Quick Start (`CuaHarness`)
36+
## Quick Start (`CuaAgentHarness`)
3737

3838
```ts
39-
import { CuaHarness } from "@onkernel/cua-agent";
39+
import { CuaAgentHarness } from "@onkernel/cua-agent";
4040

41-
const harness = new CuaHarness({
41+
const harness = new CuaAgentHarness({
4242
browser,
4343
client,
4444
model: "openai:gpt-5.5",
@@ -54,17 +54,15 @@ lifecycle events, custom streaming, and explicit prompt/continue/queue control.
5454
Reach for the harness shape when you want an app layer around the loop:
5555
session/transcript helpers, resource and prompt entry points, provider/auth
5656
hooks, active tool selection, compaction/tree workflows, and higher-level queue
57-
events. `CuaHarness` is the thin CUA version of that shape today: it installs
58-
CUA defaults, delegates runtime methods to the wrapped `Agent`, and adds
59-
`getTranscript()`.
57+
events. `CuaAgentHarness` extends pi `AgentHarness`, installs CUA defaults, and
58+
refreshes provider-specific runtime state when `setModel()` changes models.
6059

6160
## Core Concepts
6261

6362
### Class-First API
6463

6564
- `CuaAgent extends Agent`
66-
- `CuaHarness` wraps a pi `Agent` with a harness-style constructor and
67-
delegated runtime methods.
65+
- `CuaAgentHarness extends AgentHarness`
6866

6967
Both classes mirror pi constructor shapes and behavior, with minimal additions:
7068
- `browser` (Kernel browser response)
@@ -84,6 +82,17 @@ If tools are omitted, the classes install canonical CUA computer tool executors
8482
using runtime specs from `@onkernel/cua-ai`. If tools are provided, they are
8583
used exactly.
8684

85+
### Model Switching
86+
87+
`CuaAgent` follows pi `Agent` semantics: assign `agent.state.model` to a
88+
concrete model or CUA model ref. CUA-owned tools and the default system prompt
89+
refresh with the new provider runtime.
90+
91+
`CuaAgentHarness` follows pi `AgentHarness` semantics: call
92+
`await harness.setModel(model)`. The harness updates its model through pi's
93+
snapshot machinery and refreshes CUA-owned tools and default prompt state for
94+
the next provider request.
95+
8796
### Tool Composition
8897

8998
Use `createCuaComputerTools()` to compose your own tool list from canonical
@@ -105,4 +114,4 @@ const tools = [
105114
```
106115

107116
For full event semantics, steering, follow-up queues, and tool execution
108-
details, see the pi-agent-core README.
117+
details, see the pi agent core source vendored in this package.

‎packages/agent/examples/harness-openai-smoke.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Kernel from "@onkernel/sdk";
22
import { requireCuaEnvApiKeyForModel, type CuaModelRef } from "@onkernel/cua-ai";
3-
import { CuaHarness } from "../src/index";
3+
import { CuaAgentHarness } from "../src/index";
44
import { SCENARIOS } from "./shared/scenarios";
55

66
const modelRef = (process.env.MODEL_REF as CuaModelRef | undefined) ?? "openai:gpt-5.5";
@@ -13,7 +13,7 @@ async function main(): Promise<void> {
1313
const browser = await client.browsers.create({ stealth: true });
1414

1515
try {
16-
const harness = new CuaHarness({
16+
const harness = new CuaAgentHarness({
1717
browser,
1818
client,
1919
model: modelRef,

‎packages/agent/examples/harness-provider-matrix.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Kernel from "@onkernel/sdk";
22
import { requireCuaEnvApiKeyForModel, type CuaModelRef } from "@onkernel/cua-ai";
3-
import { CuaHarness } from "../src/index";
3+
import { CuaAgentHarness } from "../src/index";
44
import { SCENARIOS } from "./shared/scenarios";
55

66
const modelRef = (process.env.MODEL_REF as CuaModelRef | undefined) ?? "openai:gpt-5.5";
@@ -15,7 +15,7 @@ async function main(): Promise<void> {
1515
const scenario = SCENARIOS.find((entry) => entry.name === scenarioName) ?? SCENARIOS[0]!;
1616

1717
try {
18-
const harness = new CuaHarness({
18+
const harness = new CuaAgentHarness({
1919
browser,
2020
client,
2121
model: modelRef,

‎packages/agent/package.json‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"dist",
1717
"examples",
1818
"README.md",
19-
"CHANGELOG.md"
19+
"CHANGELOG.md",
20+
"src/vendor/pi-agent-core/LICENSE",
21+
"src/vendor/pi-agent-core/README.md"
2022
],
2123
"scripts": {
2224
"build": "tsc -b",
@@ -26,10 +28,10 @@
2628
"test": "vitest --run"
2729
},
2830
"dependencies": {
29-
"@earendil-works/pi-agent-core": "^0.74.0",
3031
"@earendil-works/pi-ai": "^0.74.0",
3132
"@onkernel/cua-ai": "0.1.0",
32-
"@onkernel/sdk": "0.49.0"
33+
"@onkernel/sdk": "0.49.0",
34+
"typebox": "^1.1.38"
3335
},
3436
"devDependencies": {
3537
"vitest": "^3.2.4"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { mkdir, writeFile } from "node:fs/promises";
2+
import { dirname, join } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
5+
const PI_COMMIT = "40c05f55391663024a6a05ad33249b616a04e7a1";
6+
const FILES = [
7+
"agent.ts",
8+
"agent-loop.ts",
9+
"harness/agent-harness.ts",
10+
"harness/compaction/branch-summarization.ts",
11+
"harness/compaction/compaction.ts",
12+
"harness/compaction/utils.ts",
13+
"harness/env/nodejs.ts",
14+
"harness/execution-env.ts",
15+
"harness/messages.ts",
16+
"harness/prompt-templates.ts",
17+
"harness/session/repo/jsonl.ts",
18+
"harness/session/repo/memory.ts",
19+
"harness/session/repo/shared.ts",
20+
"harness/session/session.ts",
21+
"harness/session/storage/jsonl.ts",
22+
"harness/session/storage/memory.ts",
23+
"harness/session/uuid.ts",
24+
"harness/skills.ts",
25+
"harness/system-prompt.ts",
26+
"harness/types.ts",
27+
"harness/utils/shell-output.ts",
28+
"harness/utils/truncate.ts",
29+
"index.ts",
30+
"proxy.ts",
31+
"types.ts",
32+
];
33+
34+
const __dirname = dirname(fileURLToPath(import.meta.url));
35+
const vendorRoot = join(__dirname, "../src/vendor/pi-agent-core");
36+
const repoRawBase = `https://raw.githubusercontent.com/badlogic/pi-mono/${PI_COMMIT}`;
37+
const rawBase = `${repoRawBase}/packages/agent/src`;
38+
39+
for (const file of FILES) {
40+
const response = await fetch(`${rawBase}/${file}`);
41+
if (!response.ok) {
42+
throw new Error(`Failed to fetch ${file}: ${response.status} ${response.statusText}`);
43+
}
44+
const outputPath = join(vendorRoot, file);
45+
await mkdir(dirname(outputPath), { recursive: true });
46+
await writeFile(outputPath, await response.text());
47+
console.log(`vendored ${file}`);
48+
}
49+
50+
const licenseResponse = await fetch(`${repoRawBase}/LICENSE`);
51+
if (!licenseResponse.ok) {
52+
throw new Error(`Failed to fetch LICENSE: ${licenseResponse.status} ${licenseResponse.statusText}`);
53+
}
54+
await writeFile(join(vendorRoot, "LICENSE"), await licenseResponse.text());
55+
console.log("vendored LICENSE");

0 commit comments

Comments
 (0)