@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,29 @@
1
+ export type AgentOutputKind = "stdout" | "stderr" | "exit";
2
+ export interface AgentOutput {
3
+ kind: AgentOutputKind;
4
+ payload: Uint8Array;
5
+ }
6
+ export interface DriveResult {
7
+ /** True while the interaction is still in flight (AcpPendingResponse). */
8
+ pending: boolean;
9
+ /** The decoded response frame bytes (the real ACP result when !pending). */
10
+ response: Uint8Array;
11
+ }
12
+ export interface AgentDriveDeps {
13
+ /** Read the next output for this execution from the reactor, servicing the
14
+ * agent's syscalls while waiting; null on timeout. */
15
+ pollAgentOutput(processId: string, deadlineMs: number): AgentOutput | null;
16
+ /** Feed a chunk of agent stdout into the resumable handshake (a non-nested
17
+ * pushFrame) and return whether it is still pending + the response frame. */
18
+ deliverAgentOutput(processId: string, chunk: Uint8Array): DriveResult;
19
+ now(): number;
20
+ }
21
+ export declare class AgentInteractionError extends Error {
22
+ constructor(message: string);
23
+ }
24
+ /**
25
+ * Drive a resumable ACP interaction to completion. Returns the real ACP result
26
+ * frame bytes (the deferred response the kernel worker relays to the client).
27
+ * Throws if the agent exits or times out before producing a result.
28
+ */
29
+ export declare function driveAgentInteraction(deps: AgentDriveDeps, processId: string, deadlineMs: number): Uint8Array;
@@ -0,0 +1,43 @@
1
+ // The kernel-worker drive loop for a resumable ACP interaction (AGENTOS-WEB-ASYNC-
2
+ // AGENTS.md §3.2.1). After a create_session / session/prompt wire frame returns
3
+ // AcpPending{processId}, the kernel worker drives the agent to completion: it reads
4
+ // the agent's stdout from the reactor (which services the agent's own syscalls in
5
+ // the meantime — legal, because we are NOT inside a pushFrame here) and feeds each
6
+ // chunk back via deliver_agent_output (a fresh, non-nested pushFrame) until the
7
+ // handshake/turn completes and yields the real result.
8
+ //
9
+ // This is pure orchestration over injected dependencies, so it is unit-testable
10
+ // without the wasm kernel, the reactor, or real workers.
11
+ export class AgentInteractionError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "AgentInteractionError";
15
+ }
16
+ }
17
+ /**
18
+ * Drive a resumable ACP interaction to completion. Returns the real ACP result
19
+ * frame bytes (the deferred response the kernel worker relays to the client).
20
+ * Throws if the agent exits or times out before producing a result.
21
+ */
22
+ export function driveAgentInteraction(deps, processId, deadlineMs) {
23
+ for (;;) {
24
+ if (deps.now() >= deadlineMs) {
25
+ throw new AgentInteractionError(`agent interaction timed out (${processId})`);
26
+ }
27
+ const out = deps.pollAgentOutput(processId, deadlineMs);
28
+ if (out === null) {
29
+ throw new AgentInteractionError(`agent produced no output before the deadline (${processId})`);
30
+ }
31
+ if (out.kind === "exit") {
32
+ throw new AgentInteractionError(`agent exited before completing the ACP interaction (${processId})`);
33
+ }
34
+ if (out.kind === "stderr") {
35
+ // stderr is diagnostic; it does not advance the JSON-RPC handshake.
36
+ continue;
37
+ }
38
+ const result = deps.deliverAgentOutput(processId, out.payload);
39
+ if (!result.pending) {
40
+ return result.response;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,55 @@
1
+ /** The slice of Chrome's built-in `LanguageModel` we use (a created session). */
2
+ export interface LanguageModelSession {
3
+ prompt(input: string): Promise<string>;
4
+ }
5
+ export declare const LANGUAGE_MODEL_OPTIONS: {
6
+ expectedInputs: {
7
+ type: string;
8
+ languages: string[];
9
+ }[];
10
+ expectedOutputs: {
11
+ type: string;
12
+ languages: string[];
13
+ }[];
14
+ };
15
+ export interface ChromeLanguageModelSessionOptions {
16
+ allowDownload?: boolean;
17
+ onDownloadProgress?(progress: number): void;
18
+ signal?: AbortSignal;
19
+ }
20
+ export declare function getChromeLanguageModelAvailability(): Promise<string>;
21
+ /** Minimal chat request shape — accepts OpenAI (`messages`) and Anthropic
22
+ * (`messages` + top-level `system`) bodies; both are `{role, content}` arrays. */
23
+ interface ChatRequest {
24
+ model?: string;
25
+ system?: string;
26
+ messages?: {
27
+ role: string;
28
+ content: unknown;
29
+ }[];
30
+ prompt?: string;
31
+ }
32
+ /** Flatten a chat request into a single prompt string for the on-device model. */
33
+ export declare function chatRequestToPrompt(request: ChatRequest): string;
34
+ /**
35
+ * Handle one chat-completion request against the on-device model. Returns an
36
+ * OpenAI-chat-completion-shaped JSON string (the shape pi's client decodes).
37
+ */
38
+ export declare function handleChatCompletion(requestBody: string, session: LanguageModelSession): Promise<string>;
39
+ /**
40
+ * MANUAL-TESTING STAND-IN ONLY — not a real model. Returns a fake
41
+ * `LanguageModelSession` whose `prompt()` yields a deterministic canned reply
42
+ * instead of calling Chrome's on-device model. This exists so the real terminal
43
+ * + pi TUI flow can be driven end-to-end on hosts where `window.LanguageModel`
44
+ * cannot be provisioned (e.g. a headless Linux box). It is OPT-IN and is NEVER
45
+ * used by the strict real-model gates (`verify:real-pi-model` /
46
+ * `verify:real-language-model`), which require a genuine `window.LanguageModel`
47
+ * answer and fail honestly when it is absent. Callers that use this must report
48
+ * `usedRealLanguageModel: false`.
49
+ */
50
+ export declare function createMockLanguageModelSession(reply?: (prompt: string) => string): LanguageModelSession;
51
+ /** Create a `LanguageModelSession` from the real Chrome `LanguageModel` global, or
52
+ * return null if it is unavailable (CI / unsupported). Probed lazily so the bundle
53
+ * loads without it. */
54
+ export declare function createChromeLanguageModelSession(options?: ChromeLanguageModelSessionOptions): Promise<LanguageModelSession | null>;
55
+ export {};
@@ -0,0 +1,135 @@
1
+ // The chrome-llm host-callback handler core (AGENTOS-WEB-ASYNC-AGENTS.md §6): the
2
+ // trusted main-thread adapter that turns a chat-completion request (the shape pi's
3
+ // HTTP client sends to its baseUrl) into a Chrome built-in `LanguageModel` call and
4
+ // formats the reply back. This is the browser twin of native's `llmock-server.mjs`,
5
+ // except backed by on-device inference instead of a mock/remote API.
6
+ //
7
+ // It is the only NEW trusted code in the inference path: the in-sandbox proxy guest
8
+ // reaches it via the existing kernel-brokered host-callback (so it is mediated by
9
+ // the policy, not an ambient capability), and pi reaches the proxy over the kernel
10
+ // loopback. pi needs ZERO changes beyond a baseUrl.
11
+ //
12
+ // Pure + injectable: the model is an interface, so a mock (returning a fixed
13
+ // sentinel) drives the deterministic CI gate and the real `LanguageModel` drives
14
+ // the best-effort Nano smoke. Non-streaming for now (single prompt → single reply).
15
+ export const LANGUAGE_MODEL_OPTIONS = {
16
+ expectedInputs: [{ type: "text", languages: ["en"] }],
17
+ expectedOutputs: [{ type: "text", languages: ["en"] }],
18
+ };
19
+ function getLanguageModelGlobal() {
20
+ return globalThis.LanguageModel;
21
+ }
22
+ export async function getChromeLanguageModelAvailability() {
23
+ const LanguageModel = getLanguageModelGlobal();
24
+ if (!LanguageModel)
25
+ return "missing-global";
26
+ try {
27
+ return await LanguageModel.availability(LANGUAGE_MODEL_OPTIONS);
28
+ }
29
+ catch (error) {
30
+ return `availability-error:${error instanceof Error ? error.message : String(error)}`;
31
+ }
32
+ }
33
+ function contentToText(content) {
34
+ if (typeof content === "string")
35
+ return content;
36
+ // Anthropic/OpenAI content can be an array of parts ({type:"text", text}).
37
+ if (Array.isArray(content)) {
38
+ return content
39
+ .map((part) => part && typeof part === "object" && "text" in part
40
+ ? String(part.text)
41
+ : typeof part === "string"
42
+ ? part
43
+ : "")
44
+ .join("");
45
+ }
46
+ return "";
47
+ }
48
+ /** Flatten a chat request into a single prompt string for the on-device model. */
49
+ export function chatRequestToPrompt(request) {
50
+ if (typeof request.prompt === "string")
51
+ return request.prompt;
52
+ const lines = [];
53
+ if (request.system)
54
+ lines.push(`system: ${request.system}`);
55
+ for (const message of request.messages ?? []) {
56
+ lines.push(`${message.role}: ${contentToText(message.content)}`);
57
+ }
58
+ return lines.join("\n");
59
+ }
60
+ /**
61
+ * Handle one chat-completion request against the on-device model. Returns an
62
+ * OpenAI-chat-completion-shaped JSON string (the shape pi's client decodes).
63
+ */
64
+ export async function handleChatCompletion(requestBody, session) {
65
+ let request;
66
+ try {
67
+ request = JSON.parse(requestBody);
68
+ }
69
+ catch {
70
+ return JSON.stringify({ error: { type: "invalid_request", message: "invalid JSON body" } });
71
+ }
72
+ const text = await session.prompt(chatRequestToPrompt(request));
73
+ return JSON.stringify({
74
+ id: "chatcmpl-chrome-local",
75
+ object: "chat.completion",
76
+ model: request.model ?? "chrome-local",
77
+ choices: [
78
+ {
79
+ index: 0,
80
+ message: { role: "assistant", content: text },
81
+ finish_reason: "stop",
82
+ },
83
+ ],
84
+ });
85
+ }
86
+ /**
87
+ * MANUAL-TESTING STAND-IN ONLY — not a real model. Returns a fake
88
+ * `LanguageModelSession` whose `prompt()` yields a deterministic canned reply
89
+ * instead of calling Chrome's on-device model. This exists so the real terminal
90
+ * + pi TUI flow can be driven end-to-end on hosts where `window.LanguageModel`
91
+ * cannot be provisioned (e.g. a headless Linux box). It is OPT-IN and is NEVER
92
+ * used by the strict real-model gates (`verify:real-pi-model` /
93
+ * `verify:real-language-model`), which require a genuine `window.LanguageModel`
94
+ * answer and fail honestly when it is absent. Callers that use this must report
95
+ * `usedRealLanguageModel: false`.
96
+ */
97
+ export function createMockLanguageModelSession(reply) {
98
+ return {
99
+ async prompt(input) {
100
+ if (reply)
101
+ return reply(input);
102
+ const lastUser = input
103
+ .split("\n")
104
+ .reverse()
105
+ .find((line) => line.trimStart().startsWith("user:"));
106
+ const echo = (lastUser ?? input).replace(/^\s*user:\s*/, "").trim();
107
+ return ("[MOCK on-device model — not real] No real window.LanguageModel is " +
108
+ "available on this host, so this is a placeholder reply for manual " +
109
+ `testing. You said: ${echo || "(nothing)"}`);
110
+ },
111
+ };
112
+ }
113
+ /** Create a `LanguageModelSession` from the real Chrome `LanguageModel` global, or
114
+ * return null if it is unavailable (CI / unsupported). Probed lazily so the bundle
115
+ * loads without it. */
116
+ export async function createChromeLanguageModelSession(options = {}) {
117
+ const LanguageModel = getLanguageModelGlobal();
118
+ if (!LanguageModel)
119
+ return null;
120
+ const availability = await getChromeLanguageModelAvailability();
121
+ if (availability !== "available" &&
122
+ !(options.allowDownload && (availability === "downloadable" || availability === "downloading"))) {
123
+ return null;
124
+ }
125
+ return LanguageModel.create({
126
+ ...LANGUAGE_MODEL_OPTIONS,
127
+ signal: options.signal,
128
+ monitor(monitor) {
129
+ monitor.addEventListener("downloadprogress", (event) => {
130
+ const progress = Number(event.loaded ?? 0);
131
+ options.onDownloadProgress?.(progress);
132
+ });
133
+ },
134
+ });
135
+ }
@@ -0,0 +1,27 @@
1
+ /** A synchronous ACP agent: each newline-delimited stdin JSON-RPC line maps to
2
+ * zero or more response lines, computed with no async I/O. */
3
+ export interface SyncAgent {
4
+ /** Handle one complete stdin line (no trailing newline); return response lines. */
5
+ handleLine(line: string): string[];
6
+ }
7
+ /** Creates a {@link SyncAgent} for one execution (the launched ACP adapter). */
8
+ export interface SyncAgentExecutor {
9
+ createAgent(request: {
10
+ executionId: string;
11
+ vmId: string;
12
+ argv: string[];
13
+ env: Record<string, string>;
14
+ cwd: string;
15
+ }): SyncAgent;
16
+ }
17
+ export interface ConvergedExecutionHostBridgeOptions {
18
+ /** When set, `startExecution` runs a synchronous in-process agent (AGENT mode). */
19
+ agentExecutor?: SyncAgentExecutor;
20
+ }
21
+ export interface ConvergedExecutionHostBridge {
22
+ /** Set the execution id `startExecution` echoes next (DRIVER mode). */
23
+ setNextExecutionId(executionId: string): void;
24
+ /** The host-bridge object passed to `new AgentOsBrowserSidecarWasm(bridge)`. */
25
+ readonly bridge: Record<string, (requestJson: string) => unknown>;
26
+ }
27
+ export declare function createConvergedExecutionHostBridge(options?: ConvergedExecutionHostBridgeOptions): ConvergedExecutionHostBridge;
@@ -0,0 +1,180 @@
1
+ // Execution host bridge for the converged Agent OS wasm sidecar.
2
+ //
3
+ // Two roles, selected by configuration:
4
+ //
5
+ // 1. DRIVER mode (default, no `agentExecutor`): mirrors secure-exec's no-op
6
+ // execution host bridge. In the converged browser runtime the guest runs in the
7
+ // browser worker (driven by @secure-exec/browser's runtime driver), not in the
8
+ // wasm sidecar; the sidecar only needs a kernel process (pid) for socket
9
+ // ownership, created by an `execute` wire request. `startExecution` echoes the
10
+ // driver-provided execution id (set via `setNextExecutionId`); the stdio
11
+ // callbacks are no-ops (the driver owns the guest's stdio).
12
+ //
13
+ // 2. AGENT mode (`agentExecutor` provided): runs a SYNCHRONOUS in-process agent for
14
+ // ACP `create_session`. Because the converged sidecar + the host-free `AcpCore`
15
+ // run synchronously on the main thread (a single `pushFrame` drives the whole
16
+ // initialize+session/new handshake), and the main thread may NOT block-wait on a
17
+ // Worker (`Atomics.wait` is forbidden there; postMessage can't arrive mid-frame),
18
+ // an *async* agent worker cannot be driven from here — see
19
+ // AGENTOS-WEB-CONVERGENCE.md Step 7. A synchronous agent (each stdin line maps to
20
+ // response lines with no async I/O, e.g. an ACP echo/test adapter) CAN: it runs
21
+ // in the same call stack, so `writeExecutionStdin` synchronously produces the
22
+ // output that the immediately-following `pollExecutionEvent` returns, and the
23
+ // synchronous `AcpCore` completes within the one `pushFrame`.
24
+ //
25
+ // The wasm `AgentOsBrowserSidecarWasm` host bridge invokes each method with a JSON
26
+ // request string and JSON-decodes the return value.
27
+ function decodeBase64ToText(base64) {
28
+ const binary = atob(base64);
29
+ const bytes = new Uint8Array(binary.length);
30
+ for (let i = 0; i < binary.length; i += 1)
31
+ bytes[i] = binary.charCodeAt(i);
32
+ return new TextDecoder().decode(bytes);
33
+ }
34
+ function encodeTextToBase64(text) {
35
+ const bytes = new TextEncoder().encode(text);
36
+ let binary = "";
37
+ for (const byte of bytes)
38
+ binary += String.fromCharCode(byte);
39
+ return btoa(binary);
40
+ }
41
+ export function createConvergedExecutionHostBridge(options = {}) {
42
+ let nextExecutionId = "converged-exec";
43
+ let contextCounter = 0;
44
+ let workerCounter = 0;
45
+ let agentCounter = 0;
46
+ const executor = options.agentExecutor;
47
+ const sessions = new Map();
48
+ const bridge = {
49
+ createJavascriptContext() {
50
+ contextCounter += 1;
51
+ return { contextId: `converged-ctx-${contextCounter}` };
52
+ },
53
+ createWasmContext() {
54
+ contextCounter += 1;
55
+ return { contextId: `converged-wasm-ctx-${contextCounter}` };
56
+ },
57
+ startExecution(requestJson) {
58
+ if (!executor) {
59
+ // DRIVER mode: echo the driver-provided id.
60
+ return { executionId: nextExecutionId };
61
+ }
62
+ // AGENT mode: mint a fresh id and instantiate the synchronous agent.
63
+ const request = parse(requestJson);
64
+ agentCounter += 1;
65
+ const executionId = `agent-exec-${agentCounter}`;
66
+ const vmId = typeof request.vmId === "string" ? request.vmId : "";
67
+ const agent = executor.createAgent({
68
+ executionId,
69
+ vmId,
70
+ argv: Array.isArray(request.argv) ? request.argv : [],
71
+ env: request.env && typeof request.env === "object"
72
+ ? request.env
73
+ : {},
74
+ cwd: typeof request.cwd === "string" ? request.cwd : "",
75
+ });
76
+ sessions.set(executionId, {
77
+ vmId,
78
+ agent,
79
+ buffer: "",
80
+ events: [],
81
+ exited: false,
82
+ });
83
+ return { executionId };
84
+ },
85
+ createWorker(requestJson) {
86
+ workerCounter += 1;
87
+ const request = parse(requestJson);
88
+ return {
89
+ workerId: `converged-worker-${workerCounter}`,
90
+ runtime: typeof request.runtime === "string" ? request.runtime : undefined,
91
+ };
92
+ },
93
+ writeExecutionStdin(requestJson) {
94
+ const request = parse(requestJson);
95
+ const executionId = typeof request.executionId === "string" ? request.executionId : "";
96
+ const session = sessions.get(executionId);
97
+ if (!session)
98
+ return {};
99
+ const chunkBase64 = typeof request.chunkBase64 === "string" ? request.chunkBase64 : "";
100
+ session.buffer += decodeBase64ToText(chunkBase64);
101
+ let newlineIndex = session.buffer.indexOf("\n");
102
+ while (newlineIndex >= 0) {
103
+ const line = session.buffer.slice(0, newlineIndex);
104
+ session.buffer = session.buffer.slice(newlineIndex + 1);
105
+ if (line.trim()) {
106
+ for (const output of session.agent.handleLine(line)) {
107
+ session.events.push({
108
+ type: "stdout",
109
+ vmId: session.vmId,
110
+ executionId,
111
+ chunkBase64: encodeTextToBase64(`${output}\n`),
112
+ });
113
+ }
114
+ }
115
+ newlineIndex = session.buffer.indexOf("\n");
116
+ }
117
+ return {};
118
+ },
119
+ closeExecutionStdin() {
120
+ return {};
121
+ },
122
+ killExecution(requestJson) {
123
+ const request = parse(requestJson);
124
+ const executionId = typeof request.executionId === "string" ? request.executionId : "";
125
+ const session = sessions.get(executionId);
126
+ if (session && !session.exited) {
127
+ session.exited = true;
128
+ session.events.push({
129
+ type: "exited",
130
+ vmId: session.vmId,
131
+ executionId,
132
+ exitCode: 0,
133
+ });
134
+ }
135
+ return {};
136
+ },
137
+ pollExecutionEvent(requestJson) {
138
+ const request = parse(requestJson);
139
+ const vmId = typeof request.vmId === "string" ? request.vmId : "";
140
+ for (const session of sessions.values()) {
141
+ if (session.vmId === vmId && session.events.length > 0) {
142
+ return session.events.shift();
143
+ }
144
+ }
145
+ return null;
146
+ },
147
+ terminateWorker() {
148
+ return {};
149
+ },
150
+ emitStructuredEvent() {
151
+ return {};
152
+ },
153
+ emitDiagnostic() {
154
+ return {};
155
+ },
156
+ emitLog() {
157
+ return {};
158
+ },
159
+ emitLifecycle() {
160
+ return {};
161
+ },
162
+ };
163
+ return {
164
+ setNextExecutionId(executionId) {
165
+ nextExecutionId = executionId;
166
+ },
167
+ bridge,
168
+ };
169
+ }
170
+ function parse(requestJson) {
171
+ try {
172
+ const value = JSON.parse(requestJson);
173
+ return typeof value === "object" && value !== null
174
+ ? value
175
+ : {};
176
+ }
177
+ catch {
178
+ return {};
179
+ }
180
+ }
@@ -0,0 +1,39 @@
1
+ import type { CreateVmConfig } from "@secure-exec/core/vm-config";
2
+ import type { ProtocolFramePayloadCodec } from "@secure-exec/core/protocol-frames";
3
+ import type { ConvergedSidecarFactoryOptions } from "@secure-exec/browser";
4
+ import { type SyncAgentExecutor } from "./converged-execution-host-bridge.js";
5
+ export interface AgentOsConvergedSidecarOptions {
6
+ /** Wire codec; defaults to the same-version BARE codec. */
7
+ codec?: ProtocolFramePayloadCodec;
8
+ /** Invoked when the kernel denies a guest fs read with EACCES. */
9
+ onFsReadDenied?: () => void;
10
+ /**
11
+ * Override the wasm glue-module URL (advanced; defaults to the bundled
12
+ * dist/sidecar-wasm-web output resolved relative to this module).
13
+ */
14
+ moduleUrl?: URL | string;
15
+ /** Override the wasm binary URL (advanced; see `moduleUrl`). */
16
+ binaryUrl?: URL | string;
17
+ /**
18
+ * Run a SYNCHRONOUS in-process ACP agent for `create_session` (e.g. an ACP
19
+ * echo/test adapter). Required to drive an agent process in-browser, because
20
+ * the synchronous main-thread `AcpCore` cannot block-wait on an async agent
21
+ * worker (see AGENTOS-WEB-CONVERGENCE.md Step 7). Omit for guest-only use.
22
+ */
23
+ agentExecutor?: SyncAgentExecutor;
24
+ }
25
+ /**
26
+ * Build the {@link ConvergedSidecarFactoryOptions} for the Agent OS web-target
27
+ * wasm kernel (with the ACP extension). Pass the result to
28
+ * `createBrowserRuntimeDriverFactory`'s `convergedSidecar` option:
29
+ *
30
+ * ```ts
31
+ * import { createBrowserRuntimeDriverFactory } from "@secure-exec/browser";
32
+ * import { createAgentOsConvergedSidecar } from "@rivet-dev/agentos-browser";
33
+ *
34
+ * const factory = createBrowserRuntimeDriverFactory({
35
+ * convergedSidecar: createAgentOsConvergedSidecar(config),
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function createAgentOsConvergedSidecar(config: CreateVmConfig, options?: AgentOsConvergedSidecarOptions): ConvergedSidecarFactoryOptions;
@@ -0,0 +1,60 @@
1
+ // Agent OS converged sidecar loader.
2
+ //
3
+ // The converged browser runtime lives in `@secure-exec/browser`: the worker, the
4
+ // SharedArrayBuffer sync-bridge, and the fs/net/dns/module servicers are all
5
+ // reused verbatim. Agent OS plugs in its OWN wasm sidecar — the one that registers
6
+ // `BrowserAcpExtension` — via `createBrowserRuntimeDriverFactory({ convergedSidecar })`.
7
+ // This is the Agent OS analogue of secure-exec's `createDefaultConvergedSidecar`:
8
+ // same `ConvergedSidecarFactoryOptions` contract, different (ACP-bearing) wasm.
9
+ //
10
+ // The kernel (wasm) remains the sole enforcement point; no guest-side permission
11
+ // eval, one transport. The agentos wasm-bindgen web output fetches its own
12
+ // `_bg.wasm`; both URLs resolve relative to this module so a consumer's bundler
13
+ // (or native ESM loader) emits/serves them.
14
+ import { createConvergedExecutionHostBridge, } from "./converged-execution-host-bridge.js";
15
+ const WASM_MODULE_URL = new URL("./sidecar-wasm-web/agentos_sidecar_browser.js", import.meta.url);
16
+ const WASM_BINARY_URL = new URL("./sidecar-wasm-web/agentos_sidecar_browser_bg.wasm", import.meta.url);
17
+ /**
18
+ * Build the {@link ConvergedSidecarFactoryOptions} for the Agent OS web-target
19
+ * wasm kernel (with the ACP extension). Pass the result to
20
+ * `createBrowserRuntimeDriverFactory`'s `convergedSidecar` option:
21
+ *
22
+ * ```ts
23
+ * import { createBrowserRuntimeDriverFactory } from "@secure-exec/browser";
24
+ * import { createAgentOsConvergedSidecar } from "@rivet-dev/agentos-browser";
25
+ *
26
+ * const factory = createBrowserRuntimeDriverFactory({
27
+ * convergedSidecar: createAgentOsConvergedSidecar(config),
28
+ * });
29
+ * ```
30
+ */
31
+ export function createAgentOsConvergedSidecar(config, options = {}) {
32
+ const moduleUrl = options.moduleUrl ?? WASM_MODULE_URL;
33
+ const binaryUrl = options.binaryUrl ?? WASM_BINARY_URL;
34
+ return {
35
+ config,
36
+ codec: options.codec ?? "bare",
37
+ onFsReadDenied: options.onFsReadDenied,
38
+ async loadSidecar() {
39
+ const host = createConvergedExecutionHostBridge({
40
+ agentExecutor: options.agentExecutor,
41
+ });
42
+ const wasmModule = (await import(
43
+ /* @vite-ignore */ String(moduleUrl)));
44
+ await wasmModule.default(String(binaryUrl));
45
+ const sidecar = new wasmModule.AgentOsBrowserSidecarWasm(host.bridge);
46
+ return {
47
+ pushFrame: (frame) => {
48
+ const response = sidecar.pushFrame(frame);
49
+ if (!(response instanceof Uint8Array)) {
50
+ throw new Error("agentos wasm sidecar returned no response frame");
51
+ }
52
+ return response;
53
+ },
54
+ setNextExecutionId: (executionId) => {
55
+ host.setNextExecutionId(executionId);
56
+ },
57
+ };
58
+ },
59
+ };
60
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
- export type { BrowserDriverOptions, BrowserRuntimeSystemOptions, } from "./driver.js";
2
- export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, listOpfsNamespaces, releaseOpfsNamespace, } from "./driver.js";
3
- export { InMemoryFileSystem } from "./os-filesystem.js";
4
- export type { ExecOptions, ExecResult, NodeRuntimeDriver, StdioChannel, StdioEvent, TimingMitigation, } from "./runtime.js";
5
- export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "./runtime.js";
6
- export type { BrowserRuntimeDriverFactoryOptions } from "./runtime-driver.js";
7
- export { createBrowserRuntimeDriverFactory } from "./runtime-driver.js";
8
- export type { WorkerHandle } from "./worker-adapter.js";
9
- export { BrowserWorkerAdapter } from "./worker-adapter.js";
1
+ export type { BrowserDriverOptions, BrowserRuntimeSystemOptions, } from "@secure-exec/browser";
2
+ export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, InMemoryFileSystem, } from "@secure-exec/browser";
3
+ export type { ExecOptions, ExecResult, NodeRuntimeDriver, StdioChannel, StdioEvent, TimingMitigation, } from "@secure-exec/browser";
4
+ export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "@secure-exec/browser";
5
+ export type { BrowserRuntimeDriverFactoryOptions, ConvergedSidecarFactoryOptions, ConvergedSidecarHandle, } from "@secure-exec/browser";
6
+ export { createBrowserRuntimeDriverFactory } from "@secure-exec/browser";
7
+ export type { WorkerHandle } from "@secure-exec/browser";
8
+ export { BrowserWorkerAdapter } from "@secure-exec/browser";
9
+ export type { AgentOsConvergedSidecarOptions } from "./converged-sidecar.js";
10
+ export { createAgentOsConvergedSidecar } from "./converged-sidecar.js";
11
+ export type { ConvergedExecutionHostBridge } from "./converged-execution-host-bridge.js";
12
+ export { createConvergedExecutionHostBridge } from "./converged-execution-host-bridge.js";
package/dist/index.js CHANGED
@@ -1,5 +1,19 @@
1
- export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, listOpfsNamespaces, releaseOpfsNamespace, } from "./driver.js";
2
- export { InMemoryFileSystem } from "./os-filesystem.js";
3
- export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "./runtime.js";
4
- export { createBrowserRuntimeDriverFactory } from "./runtime-driver.js";
5
- export { BrowserWorkerAdapter } from "./worker-adapter.js";
1
+ // @rivet-dev/agentos-browser converged browser runtime for Agent OS.
2
+ //
3
+ // The browser runtime is @secure-exec/browser's CONVERGED stack (worker,
4
+ // SharedArrayBuffer sync-bridge, fs/net/dns/module servicers, all enforced by the
5
+ // wasm kernel). Agent OS does not carry its own copy; it re-exports that runtime
6
+ // and adds only the ACP/wasm-sidecar layer (createAgentOsConvergedSidecar). The
7
+ // pre-convergence TS-kernel files (worker/runtime/driver/sync-bridge/permission
8
+ // eval) were deleted in the reconciliation — the kernel is the sole enforcement
9
+ // point, so guest-side permission eval no longer exists here.
10
+ //
11
+ // Per the converged model (kernel-owns-fs), per-runtime OPFS *namespace* helpers
12
+ // (listOpfsNamespaces/releaseOpfsNamespace) are gone: storage isolation is the
13
+ // kernel's responsibility, not a TS-layer concern.
14
+ export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, InMemoryFileSystem, } from "@secure-exec/browser";
15
+ export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "@secure-exec/browser";
16
+ export { createBrowserRuntimeDriverFactory } from "@secure-exec/browser";
17
+ export { BrowserWorkerAdapter } from "@secure-exec/browser";
18
+ export { createAgentOsConvergedSidecar } from "./converged-sidecar.js";
19
+ export { createConvergedExecutionHostBridge } from "./converged-execution-host-bridge.js";
@@ -0,0 +1,23 @@
1
+ export interface HttpRequest {
2
+ method: string;
3
+ path: string;
4
+ headers: Map<string, string>;
5
+ body: string;
6
+ }
7
+ export interface HttpResponse {
8
+ status: number;
9
+ headers: Map<string, string>;
10
+ body: string;
11
+ }
12
+ /** Build a minimal HTTP/1.1 request (the client/pi side). */
13
+ export declare function buildHttpRequest(method: string, path: string, body: string, host?: string): Uint8Array;
14
+ /** Build a minimal HTTP/1.1 response (the proxy side). */
15
+ export declare function buildHttpResponse(status: number, body: string): Uint8Array;
16
+ /** Parse a complete HTTP request, reading more via `readChunk` until the body is in. */
17
+ export declare function readHttpRequest(initial: Uint8Array, readChunk: () => Uint8Array | null): HttpRequest | null;
18
+ /** Parse a complete HTTP response (the client/pi side). */
19
+ export declare function readHttpResponse(initial: Uint8Array, readChunk: () => Uint8Array | null): HttpResponse | null;
20
+ /** The proxy core: turn one parsed HTTP request into an HTTP response by forwarding
21
+ * the body to `infer` (the host.inference call). Only POSTs to a chat/completions or
22
+ * /v1/messages path are forwarded; anything else is a 404-shaped error. */
23
+ export declare function handleProxyRequest(request: HttpRequest, infer: (body: string) => Promise<string> | string): Promise<Uint8Array>;