@rivet-dev/agentos-browser 0.2.4 → 0.2.5-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,115 @@
1
+ // The in-sandbox OpenAI proxy logic (AGENTOS-WEB-ASYNC-AGENTS.md §6). pi (and any
2
+ // OpenAI/Anthropic HTTP client) talks to its `baseUrl` over plain loopback HTTP; a
3
+ // guest proxy listening on that port forwards the request body to on-device inference
4
+ // through the kernel-brokered `host.inference` syscall and returns the reply as an
5
+ // HTTP response. pi needs ZERO changes beyond the baseUrl.
6
+ //
7
+ // This module is the PURE framing + forwarding logic, free of any socket/syscall
8
+ // dependency (those are injected), so it is unit-testable in Node and reusable by both
9
+ // the test proxy agent and the eventual standalone proxy guest. The proxy does not
10
+ // transform the chat body — it is HTTP framing around the inference call: the request
11
+ // body IS the chat-completion JSON the chrome-llm adapter consumes, and that adapter's
12
+ // reply IS the HTTP response body.
13
+ const encoder = new TextEncoder();
14
+ const decoder = new TextDecoder();
15
+ /** Build a minimal HTTP/1.1 request (the client/pi side). */
16
+ export function buildHttpRequest(method, path, body, host = "127.0.0.1") {
17
+ const bodyBytes = encoder.encode(body);
18
+ const head = `${method} ${path} HTTP/1.1\r\n` +
19
+ `Host: ${host}\r\n` +
20
+ `Content-Type: application/json\r\n` +
21
+ `Content-Length: ${bodyBytes.byteLength}\r\n` +
22
+ `Connection: close\r\n\r\n`;
23
+ return concat(encoder.encode(head), bodyBytes);
24
+ }
25
+ /** Build a minimal HTTP/1.1 response (the proxy side). */
26
+ export function buildHttpResponse(status, body) {
27
+ const bodyBytes = encoder.encode(body);
28
+ const reason = status === 200 ? "OK" : status === 400 ? "Bad Request" : "Error";
29
+ const head = `HTTP/1.1 ${status} ${reason}\r\n` +
30
+ `Content-Type: application/json\r\n` +
31
+ `Content-Length: ${bodyBytes.byteLength}\r\n` +
32
+ `Connection: close\r\n\r\n`;
33
+ return concat(encoder.encode(head), bodyBytes);
34
+ }
35
+ /** Index of the end of the header block (after the CRLFCRLF), or -1 if not yet present. */
36
+ function headerEnd(bytes) {
37
+ for (let i = 3; i < bytes.length; i += 1) {
38
+ if (bytes[i - 3] === 13 && bytes[i - 2] === 10 && bytes[i - 1] === 13 && bytes[i] === 10) {
39
+ return i + 1;
40
+ }
41
+ }
42
+ return -1;
43
+ }
44
+ function parseHeaders(headerText) {
45
+ const lines = headerText.split("\r\n").filter((l) => l.length > 0);
46
+ const startLine = lines.shift() ?? "";
47
+ const headers = new Map();
48
+ for (const line of lines) {
49
+ const colon = line.indexOf(":");
50
+ if (colon > 0)
51
+ headers.set(line.slice(0, colon).trim().toLowerCase(), line.slice(colon + 1).trim());
52
+ }
53
+ return { startLine, headers };
54
+ }
55
+ function contentLength(headers) {
56
+ const raw = headers.get("content-length");
57
+ const n = raw ? Number.parseInt(raw, 10) : 0;
58
+ return Number.isFinite(n) && n >= 0 ? n : 0;
59
+ }
60
+ /** Read a full HTTP message (headers + Content-Length body) by pulling chunks from
61
+ * `readChunk` until complete. `readChunk()` returns the next bytes (possibly empty if
62
+ * not ready yet) or null on EOF. Returns the assembled bytes + the body offset. */
63
+ function readFullMessage(initial, readChunk) {
64
+ let buf = initial;
65
+ for (let guard = 0; guard < 100_000; guard += 1) {
66
+ const bodyStart = headerEnd(buf);
67
+ if (bodyStart >= 0) {
68
+ const { headers } = parseHeaders(decoder.decode(buf.subarray(0, bodyStart)));
69
+ const need = bodyStart + contentLength(headers);
70
+ if (buf.byteLength >= need)
71
+ return { bytes: buf.subarray(0, need), bodyStart };
72
+ }
73
+ const chunk = readChunk();
74
+ if (chunk === null)
75
+ return bodyStart >= 0 ? { bytes: buf, bodyStart } : null;
76
+ if (chunk.byteLength > 0)
77
+ buf = concat(buf, chunk);
78
+ }
79
+ return null;
80
+ }
81
+ /** Parse a complete HTTP request, reading more via `readChunk` until the body is in. */
82
+ export function readHttpRequest(initial, readChunk) {
83
+ const message = readFullMessage(initial, readChunk);
84
+ if (!message)
85
+ return null;
86
+ const { startLine, headers } = parseHeaders(decoder.decode(message.bytes.subarray(0, message.bodyStart)));
87
+ const [method = "", path = ""] = startLine.split(" ");
88
+ return { method, path, headers, body: decoder.decode(message.bytes.subarray(message.bodyStart)) };
89
+ }
90
+ /** Parse a complete HTTP response (the client/pi side). */
91
+ export function readHttpResponse(initial, readChunk) {
92
+ const message = readFullMessage(initial, readChunk);
93
+ if (!message)
94
+ return null;
95
+ const { startLine, headers } = parseHeaders(decoder.decode(message.bytes.subarray(0, message.bodyStart)));
96
+ const status = Number.parseInt(startLine.split(" ")[1] ?? "0", 10) || 0;
97
+ return { status, headers, body: decoder.decode(message.bytes.subarray(message.bodyStart)) };
98
+ }
99
+ /** The proxy core: turn one parsed HTTP request into an HTTP response by forwarding
100
+ * the body to `infer` (the host.inference call). Only POSTs to a chat/completions or
101
+ * /v1/messages path are forwarded; anything else is a 404-shaped error. */
102
+ export async function handleProxyRequest(request, infer) {
103
+ const isChat = request.method === "POST" && /\/(chat\/completions|messages|completions)$/.test(request.path);
104
+ if (!isChat) {
105
+ return buildHttpResponse(404, JSON.stringify({ error: { type: "not_found", message: `no handler for ${request.method} ${request.path}` } }));
106
+ }
107
+ const reply = await infer(request.body);
108
+ return buildHttpResponse(200, reply);
109
+ }
110
+ function concat(a, b) {
111
+ const out = new Uint8Array(a.byteLength + b.byteLength);
112
+ out.set(a, 0);
113
+ out.set(b, a.byteLength);
114
+ return out;
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivet-dev/agentos-browser",
3
- "version": "0.2.4",
3
+ "version": "0.2.5-rc.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
@@ -14,56 +14,34 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js",
16
16
  "default": "./dist/index.js"
17
- },
18
- "./internal/driver": {
19
- "types": "./dist/driver.d.ts",
20
- "import": "./dist/driver.js",
21
- "default": "./dist/driver.js"
22
- },
23
- "./internal/runtime-driver": {
24
- "types": "./dist/runtime-driver.d.ts",
25
- "import": "./dist/runtime-driver.js",
26
- "default": "./dist/runtime-driver.js"
27
- },
28
- "./internal/worker": {
29
- "types": "./dist/worker.d.ts",
30
- "import": "./dist/worker.js",
31
- "default": "./dist/worker.js"
32
- },
33
- "./internal/worker-protocol": {
34
- "types": "./dist/worker-protocol.d.ts",
35
- "import": "./dist/worker-protocol.js",
36
- "default": "./dist/worker-protocol.js"
37
- },
38
- "./internal/permission-validation": {
39
- "types": "./dist/permission-validation.d.ts",
40
- "import": "./dist/permission-validation.js",
41
- "default": "./dist/permission-validation.js"
42
- },
43
- "./internal/os-filesystem": {
44
- "types": "./dist/os-filesystem.d.ts",
45
- "import": "./dist/os-filesystem.js",
46
- "default": "./dist/os-filesystem.js"
47
- },
48
- "./internal/worker-adapter": {
49
- "types": "./dist/worker-adapter.d.ts",
50
- "import": "./dist/worker-adapter.js",
51
- "default": "./dist/worker-adapter.js"
52
17
  }
53
18
  },
54
19
  "scripts": {
55
- "check-types": "tsc --noEmit",
56
- "build": "tsc",
57
- "test:browser": "node ./scripts/run-browser-tests.mjs",
58
- "test": "pnpm build && vitest run tests/runtime-driver && pnpm run test:browser"
20
+ "check-types": "pnpm check:converged-gates && tsc --noEmit",
21
+ "check:converged-gates": "node ./scripts/check-converged-gates.mjs",
22
+ "dev": "vite",
23
+ "build": "tsc && pnpm build:dist-wasm",
24
+ "build:dist-wasm": "node ./scripts/build-dist-wasm.mjs",
25
+ "build:sidecar-wasm": "node ./scripts/build-sidecar-wasm.mjs",
26
+ "build:wasm-test-assets": "node ./scripts/build-wasm-test-assets.mjs",
27
+ "verify:real-language-model": "node ./scripts/verify-real-language-model.mjs",
28
+ "verify:real-pi-tui": "node ./scripts/verify-real-pi-tui.mjs",
29
+ "verify:real-pi-model": "node ./scripts/verify-real-pi-model.mjs",
30
+ "test:browser-wasm": "playwright test --config=playwright.wasm.config.ts",
31
+ "test": "pnpm check:converged-gates && pnpm build && vitest run tests/runtime-driver && pnpm run test:browser-wasm"
59
32
  },
60
33
  "dependencies": {
34
+ "@secure-exec/browser": "0.3.4-rc.1",
35
+ "@secure-exec/core": "0.3.4-rc.1",
61
36
  "sucrase": "^3.35.0"
62
37
  },
63
38
  "devDependencies": {
64
39
  "@playwright/test": "^1.54.2",
65
40
  "@types/node": "^22.10.2",
41
+ "@xterm/addon-fit": "0.10.0",
42
+ "@xterm/xterm": "5.5.0",
66
43
  "typescript": "^5.7.2",
44
+ "vite": "^6.4.3",
67
45
  "vitest": "^2.1.8"
68
46
  }
69
47
  }
package/dist/driver.d.ts DELETED
@@ -1,114 +0,0 @@
1
- import type { NetworkAdapter, Permissions, SystemDriver, VirtualFileSystem } from "./runtime.js";
2
- import { createCommandExecutorStub, createFsStub, createNetworkStub } from "./runtime.js";
3
- export interface BrowserRuntimeSystemOptions {
4
- filesystem: "opfs" | "memory";
5
- networkEnabled: boolean;
6
- }
7
- export declare function releaseOpfsNamespace(namespace: string): Promise<void>;
8
- export declare function listOpfsNamespaces(): Promise<string[]>;
9
- /**
10
- * VFS backed by the Origin Private File System (OPFS) API. Falls back to
11
- * InMemoryFileSystem when OPFS is unavailable. Rename is not supported
12
- * (throws ENOSYS) since OPFS doesn't provide atomic rename.
13
- */
14
- export declare class OpfsFileSystem implements VirtualFileSystem {
15
- private rootPromise;
16
- readonly namespace: string;
17
- /**
18
- * @param namespace Unique per-runtime/tenant OPFS namespace. Defaults to a
19
- * freshly generated id so co-resident runtimes never share storage (F-015).
20
- * Pass a stable id to persist a runtime's data across instances.
21
- */
22
- constructor(namespace?: string);
23
- private getDirHandle;
24
- private getFileHandle;
25
- readFile(path: string): Promise<Uint8Array>;
26
- readTextFile(path: string): Promise<string>;
27
- readDir(path: string): Promise<string[]>;
28
- readDirWithTypes(path: string): Promise<Array<{
29
- name: string;
30
- isDirectory: boolean;
31
- }>>;
32
- writeFile(path: string, content: string | Uint8Array): Promise<void>;
33
- createDir(path: string): Promise<void>;
34
- mkdir(path: string, _options?: {
35
- recursive?: boolean;
36
- }): Promise<void>;
37
- exists(path: string): Promise<boolean>;
38
- stat(path: string): Promise<{
39
- mode: number;
40
- size: number;
41
- blocks: number;
42
- dev: number;
43
- rdev: number;
44
- isDirectory: boolean;
45
- isSymbolicLink: boolean;
46
- atimeMs: number;
47
- mtimeMs: number;
48
- ctimeMs: number;
49
- birthtimeMs: number;
50
- ino: number;
51
- nlink: number;
52
- uid: number;
53
- gid: number;
54
- }>;
55
- removeFile(path: string): Promise<void>;
56
- removeDir(path: string): Promise<void>;
57
- rename(_oldPath: string, _newPath: string): Promise<void>;
58
- symlink(_target: string, _linkPath: string): Promise<void>;
59
- readlink(_path: string): Promise<string>;
60
- lstat(path: string): Promise<{
61
- mode: number;
62
- size: number;
63
- blocks: number;
64
- dev: number;
65
- rdev: number;
66
- isDirectory: boolean;
67
- isSymbolicLink: boolean;
68
- atimeMs: number;
69
- mtimeMs: number;
70
- ctimeMs: number;
71
- birthtimeMs: number;
72
- ino: number;
73
- nlink: number;
74
- uid: number;
75
- gid: number;
76
- }>;
77
- link(_oldPath: string, _newPath: string): Promise<void>;
78
- chmod(_path: string, _mode: number): Promise<void>;
79
- chown(_path: string, _uid: number, _gid: number): Promise<void>;
80
- utimes(_path: string, _atime: number, _mtime: number): Promise<void>;
81
- truncate(path: string, length: number): Promise<void>;
82
- realpath(path: string): Promise<string>;
83
- pread(path: string, offset: number, length: number): Promise<Uint8Array>;
84
- pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;
85
- }
86
- export interface BrowserDriverOptions {
87
- filesystem?: "opfs" | "memory";
88
- permissions?: Permissions;
89
- useDefaultNetwork?: boolean;
90
- /**
91
- * Per-runtime/tenant OPFS namespace. Defaults to a freshly generated id so
92
- * co-resident runtimes never share storage (F-015). Pass a stable id to
93
- * persist a runtime's data across driver instances. Generated default
94
- * namespaces are not garbage-collected automatically; long-lived embedders
95
- * should either pass a stable namespace or call `releaseOpfsNamespace`.
96
- */
97
- opfsNamespace?: string;
98
- }
99
- /**
100
- * Create an OPFS-backed filesystem, falling back to in-memory if OPFS is
101
- * unavailable. Each instance is namespaced under a unique per-runtime
102
- * subdirectory (F-015); pass `namespace` to use a stable, persistent namespace.
103
- */
104
- export declare function createOpfsFileSystem(namespace?: string): Promise<VirtualFileSystem>;
105
- export interface BrowserNetworkAdapterOptions {
106
- fetch?: typeof fetch;
107
- }
108
- /** Network adapter that delegates to the browser's native `fetch`. DNS and http2 are unsupported. */
109
- export declare function createBrowserNetworkAdapter(options?: BrowserNetworkAdapterOptions): NetworkAdapter;
110
- /** Recover runtime-driver options from a browser SystemDriver instance. */
111
- export declare function getBrowserSystemDriverOptions(systemDriver: SystemDriver): BrowserRuntimeSystemOptions;
112
- /** Assemble a browser-side SystemDriver with permission-wrapped adapters. */
113
- export declare function createBrowserDriver(options?: BrowserDriverOptions): Promise<SystemDriver>;
114
- export { createCommandExecutorStub, createFsStub, createNetworkStub };