@pi-oxide/pi-host-web 0.1.0 → 0.2.1

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.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @pi-oxide/pi-host-web
2
+
3
+ WASM host for [pi-core](https://github.com/pi-oxide/pi-oxide) — a deterministic agent state machine, compiled to WebAssembly for browser and Node.js.
4
+
5
+ ## What it is
6
+
7
+ This package exposes the `pi-core` agent loop through typed JavaScript APIs. Every function returns a strongly-typed result envelope — never throws. The TypeScript definitions are generated directly from Rust structs via [tsify](https://github.com/madonoharu/tsify).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @pi-oxide/pi-host-web
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Browser
18
+
19
+ ```typescript
20
+ import init, * as wasm from "@pi-oxide/pi-host-web";
21
+
22
+ // Initialize the WASM module (async, required once)
23
+ await init();
24
+
25
+ // Create an agent
26
+ const result = wasm.createAgent({
27
+ system_prompt: "You are a helpful assistant.",
28
+ model: {
29
+ id: "claude-sonnet-4-20250514",
30
+ name: "Claude Sonnet",
31
+ api: "anthropic",
32
+ provider: "anthropic",
33
+ reasoning: false,
34
+ context_window: 200000,
35
+ max_tokens: 4096,
36
+ },
37
+ });
38
+
39
+ const handle = result.data.handle;
40
+
41
+ // Send a prompt
42
+ const step = wasm.prompt(handle, { text: "Hello!" });
43
+ console.log(step.data.actions);
44
+ ```
45
+
46
+ ### Node.js
47
+
48
+ ```typescript
49
+ import { readFileSync } from "node:fs";
50
+ import { createRequire } from "node:module";
51
+ import { dirname, join } from "node:path";
52
+
53
+ const require = createRequire(import.meta.url);
54
+ const pkgDir = dirname(require.resolve("@pi-oxide/pi-host-web/package.json"));
55
+ const wasmPath = join(pkgDir, "pi_host_web_bg.wasm");
56
+ const wasmBytes = readFileSync(wasmPath);
57
+
58
+ const pkg = await import("@pi-oxide/pi-host-web");
59
+ pkg.initSync({ module: wasmBytes });
60
+
61
+ // Now use pkg.createAgent(), pkg.prompt(), etc.
62
+ const result = pkg.createAgent({ ... });
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### Agent lifecycle
68
+
69
+ | Function | Input | Returns | Description |
70
+ |----------|-------|---------|-------------|
71
+ | `createAgent(options)` | `AgentOptions` | `CreateAgentResult` | Creates a new agent instance |
72
+ | `destroyAgent(handle)` | `number` | `EmptyResult` | Destroys an agent and frees its slot |
73
+ | `reset(handle)` | `number` | `EmptyResult` | Resets agent state (keeps config) |
74
+ | `state(handle)` | `number` | `StateResult` | Returns current agent state |
75
+
76
+ ### Turn loop
77
+
78
+ | Function | Input | Returns | Description |
79
+ |----------|-------|---------|-------------|
80
+ | `prompt(handle, request)` | `number`, `PromptRequest` | `StepResult` | Starts a new turn |
81
+ | `feedLlmChunk(handle, chunk)` | `number`, `LlmChunk` | `EventsResult` | Feeds a streaming LLM chunk |
82
+ | `onLlmDone(handle, result)` | `number`, `LlmResult` | `StepResult` | Signals LLM stream completion |
83
+ | `onToolDone(handle, id, payload)` | `number`, `string`, `ToolDonePayload` | `StepResult` | Reports tool execution result |
84
+ | `onToolStarted(handle, id)` | `number`, `string` | `EventsResult` | Signals tool execution started |
85
+ | `onToolUpdate(handle, update)` | `number`, `ToolExecutionUpdate` | `EventsResult` | Streams tool stdout/stderr |
86
+ | `onToolCancelled(handle, id, reason)` | `number`, `string`, `CancelReason` | `StepResult` | Cancels a running tool |
87
+
88
+ ### Context projection
89
+
90
+ | Function | Input | Returns | Description |
91
+ |----------|-------|---------|-------------|
92
+ | `projectContext(input)` | `ProjectionInput` | `ProjectionResult` | Projects context to fit budget |
93
+
94
+ ### Steering
95
+
96
+ | Function | Input | Returns | Description |
97
+ |----------|-------|---------|-------------|
98
+ | `steer(handle, message)` | `number`, `AgentMessage` | `EventsResult` | Injects a steering message |
99
+ | `followUp(handle, message)` | `number`, `AgentMessage` | `EmptyResult` | Appends a follow-up message |
100
+
101
+ ### Observability
102
+
103
+ | Function | Returns | Description |
104
+ |----------|---------|-------------|
105
+ | `drainTraceLog()` | `string[]` | Drains and clears the Rust trace buffer |
106
+
107
+ ## Result envelopes
108
+
109
+ Every function returns a typed result with this shape:
110
+
111
+ ```typescript
112
+ interface Result<T> {
113
+ ok: boolean;
114
+ data?: T;
115
+ error?: {
116
+ code: string;
117
+ message: string;
118
+ };
119
+ }
120
+ ```
121
+
122
+ Concrete types: `CreateAgentResult`, `StepResult`, `EventsResult`, `StateResult`, `EmptyResult`, `ProjectionResult`.
123
+
124
+ ## Files
125
+
126
+ - `pi_host_web.js` — Main ESM entry point
127
+ - `pi_host_web_bg.wasm` — Compiled WebAssembly binary
128
+ - `pi_host_web.d.ts` — TypeScript declarations (generated from Rust)
129
+
130
+ ## License
131
+
132
+ MIT
package/package.json CHANGED
@@ -5,20 +5,33 @@
5
5
  "Irving Ou <irving@pi-oxide.dev>"
6
6
  ],
7
7
  "description": "WASM host for pi-core. Browser FileSystem Access API, fetch(), JS event loop.",
8
- "version": "0.1.0",
8
+ "version": "0.2.1",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/pi-oxide/pi-oxide"
12
+ "url": "git+https://github.com/pi-oxide/pi-oxide.git"
13
13
  },
14
14
  "files": [
15
15
  "pi_host_web_bg.wasm",
16
16
  "pi_host_web.js",
17
- "pi_host_web.d.ts"
17
+ "pi_host_web.d.ts",
18
+ "sdk/index.js",
19
+ "sdk/index.d.ts"
18
20
  ],
19
21
  "main": "pi_host_web.js",
20
22
  "types": "pi_host_web.d.ts",
21
23
  "sideEffects": [
22
24
  "./snippets/*"
23
- ]
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "import": "./sdk/index.js",
29
+ "types": "./sdk/index.d.ts"
30
+ },
31
+ "./raw": {
32
+ "import": "./pi_host_web.js",
33
+ "types": "./pi_host_web.d.ts"
34
+ },
35
+ "./package.json": "./package.json"
36
+ }
24
37
  }
package/pi_host_web.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface AgentOptions {
16
16
  tool_execution_mode?: ToolExecutionMode;
17
17
  session_id?: SessionId;
18
18
  messages?: AgentMessage[];
19
+ session_state?: SessionState | null;
19
20
  }
20
21
 
21
22
  export interface AgentState {
@@ -46,10 +47,9 @@ export interface AssistantMessage {
46
47
  usage: TokenUsage;
47
48
  }
48
49
 
49
- export interface BackgroundJobRef {
50
- job_id: string;
51
- tool_call_id: ToolCallId;
52
- command_label: string;
50
+ export interface BranchSummary {
51
+ summary: string;
52
+ details?: JsonSchema;
53
53
  }
54
54
 
55
55
  export interface ContextProjectionBudget {
@@ -104,6 +104,20 @@ export interface ErrorDto {
104
104
  message: string;
105
105
  }
106
106
 
107
+ export interface EstimateTokensInput {
108
+ messages: AgentMessage[];
109
+ }
110
+
111
+ export interface EstimateTokensOutput {
112
+ tokens: number;
113
+ }
114
+
115
+ export interface EstimateTokensResult {
116
+ ok: boolean;
117
+ data?: EstimateTokensOutput;
118
+ error?: ErrorDto;
119
+ }
120
+
107
121
  export interface EventsOutput {
108
122
  events: AgentEvent[];
109
123
  }
@@ -158,6 +172,16 @@ export interface ModelCost {
158
172
  cache_write: number;
159
173
  }
160
174
 
175
+ export interface MoveToOutput {
176
+ summary_entry_id: string | null;
177
+ }
178
+
179
+ export interface MoveToResult {
180
+ ok: boolean;
181
+ data?: MoveToOutput;
182
+ error?: ErrorDto;
183
+ }
184
+
161
185
  export interface ProjectionInput {
162
186
  system_prompt: string;
163
187
  messages: AgentMessage[];
@@ -177,6 +201,39 @@ export interface ProjectionResult {
177
201
  error?: ErrorDto;
178
202
  }
179
203
 
204
+ export interface SessionBranchOutput {
205
+ entries: SessionEntry[];
206
+ }
207
+
208
+ export interface SessionBranchResult {
209
+ ok: boolean;
210
+ data?: SessionBranchOutput;
211
+ error?: ErrorDto;
212
+ }
213
+
214
+ export interface SessionEntry {
215
+ id: string;
216
+ parent_id?: string;
217
+ kind: EntryKind;
218
+ timestamp: number;
219
+ }
220
+
221
+ export interface SessionState {
222
+ entries: SessionEntry[];
223
+ leaf_id: string;
224
+ name?: string;
225
+ }
226
+
227
+ export interface SessionStateOutput {
228
+ state: SessionState;
229
+ }
230
+
231
+ export interface SessionStateResult {
232
+ ok: boolean;
233
+ data?: SessionStateOutput;
234
+ error?: ErrorDto;
235
+ }
236
+
180
237
  export interface StateOutput {
181
238
  state: AgentState;
182
239
  }
@@ -285,6 +342,8 @@ export type ContentKind = "file_read" | "command_output" | "diff" | "search_resu
285
342
 
286
343
  export type ContextStrategy = { type: "keep_full" } | { type: "head"; max_chars: number } | { type: "tail"; max_chars: number } | { type: "head_tail"; head_chars: number; tail_chars: number } | { type: "drop_if_old" } | { type: "microcompacted" };
287
344
 
345
+ export type EntryKind = ({ type: "message" } & {} & AgentMessage) | { type: "compaction"; summary: string; first_kept_entry_id: string; tokens_before: number; details?: JsonSchema } | { type: "branch_summary"; summary: string; details?: JsonSchema } | { type: "model_change"; provider: string; model_id: string } | ({ type: "thinking_level_change" } & ThinkingLevel) | { type: "custom"; custom_type: string; data?: JsonSchema };
346
+
288
347
  export type ExecutionMode = "parallel" | "sequential";
289
348
 
290
349
  export type JsonSchema = Value;
@@ -299,7 +358,7 @@ export type ModelName = string;
299
358
 
300
359
  export type ModelProvider = "open_ai" | "anthropic" | "google" | "ollama" | "custom";
301
360
 
302
- export type Phase = "idle" | "streaming" | "executing_tools" | "wait_for_input";
361
+ export type Phase = "idle" | "streaming" | "wait_for_input";
303
362
 
304
363
  export type PromptRequest = AgentMessage | { text: string };
305
364
 
@@ -330,16 +389,28 @@ export type ToolOutputStream = "stdout" | "stderr" | "status";
330
389
  export type WaitMode = "steering" | "follow_up" | "any";
331
390
 
332
391
 
392
+ export function appendSessionEntry(handle: number, entry: SessionEntry): EmptyResult;
393
+
333
394
  export function createAgent(options: AgentOptions): CreateAgentResult;
334
395
 
335
396
  export function destroyAgent(handle: number): EmptyResult;
336
397
 
337
- export function drainTraceLog(): string[];
398
+ export function estimateTokens(input: EstimateTokensInput): EstimateTokensResult;
399
+
400
+ export function estimateTokensForText(text: string): EstimateTokensResult;
401
+
402
+ export function fallbackStrategy(tool_name: string): ContextStrategy;
338
403
 
339
404
  export function feedLlmChunk(handle: number, chunk: LlmChunk): EventsResult;
340
405
 
341
406
  export function followUp(handle: number, message: AgentMessage): EmptyResult;
342
407
 
408
+ export function getSessionBranch(handle: number): SessionBranchResult;
409
+
410
+ export function getSessionState(handle: number): SessionStateResult;
411
+
412
+ export function moveTo(handle: number, target_id: string, summary?: BranchSummary | null): MoveToResult;
413
+
343
414
  export function onLlmDone(handle: number, result: LlmResult): StepResult;
344
415
 
345
416
  export function onToolCancelled(handle: number, tool_call_id: string, reason: CancelReason): StepResult;
@@ -356,6 +427,10 @@ export function prompt(handle: number, prompt: PromptRequest): StepResult;
356
427
 
357
428
  export function reset(handle: number): EmptyResult;
358
429
 
430
+ export function setLogLevel(level: string): void;
431
+
432
+ export function setSessionState(handle: number, state: SessionState): EmptyResult;
433
+
359
434
  export function state(handle: number): StateResult;
360
435
 
361
436
  export function steer(handle: number, message: AgentMessage): EventsResult;
@@ -364,11 +439,17 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
364
439
 
365
440
  export interface InitOutput {
366
441
  readonly memory: WebAssembly.Memory;
442
+ readonly appendSessionEntry: (a: number, b: any) => any;
367
443
  readonly createAgent: (a: any) => any;
368
444
  readonly destroyAgent: (a: number) => any;
369
- readonly drainTraceLog: () => [number, number];
445
+ readonly estimateTokens: (a: any) => any;
446
+ readonly estimateTokensForText: (a: number, b: number) => any;
447
+ readonly fallbackStrategy: (a: number, b: number) => any;
370
448
  readonly feedLlmChunk: (a: number, b: any) => any;
371
449
  readonly followUp: (a: number, b: any) => any;
450
+ readonly getSessionBranch: (a: number) => any;
451
+ readonly getSessionState: (a: number) => any;
452
+ readonly moveTo: (a: number, b: number, c: number, d: number) => any;
372
453
  readonly onLlmDone: (a: number, b: any) => any;
373
454
  readonly onToolCancelled: (a: number, b: number, c: number, d: any) => any;
374
455
  readonly onToolDone: (a: number, b: number, c: number, d: any) => any;
@@ -377,6 +458,8 @@ export interface InitOutput {
377
458
  readonly projectContext: (a: any) => any;
378
459
  readonly prompt: (a: number, b: any) => any;
379
460
  readonly reset: (a: number) => any;
461
+ readonly setLogLevel: (a: number, b: number) => void;
462
+ readonly setSessionState: (a: number, b: any) => any;
380
463
  readonly state: (a: number) => any;
381
464
  readonly steer: (a: number, b: any) => any;
382
465
  readonly __wbindgen_malloc: (a: number, b: number) => number;
@@ -385,7 +468,6 @@ export interface InitOutput {
385
468
  readonly __wbindgen_exn_store: (a: number) => void;
386
469
  readonly __externref_table_alloc: () => number;
387
470
  readonly __wbindgen_externrefs: WebAssembly.Table;
388
- readonly __externref_drop_slice: (a: number, b: number) => void;
389
471
  readonly __wbindgen_start: () => void;
390
472
  }
391
473
 
package/pi_host_web.js CHANGED
@@ -1,5 +1,15 @@
1
1
  /* @ts-self-types="./pi_host_web.d.ts" */
2
2
 
3
+ /**
4
+ * @param {number} handle
5
+ * @param {SessionEntry} entry
6
+ * @returns {EmptyResult}
7
+ */
8
+ export function appendSessionEntry(handle, entry) {
9
+ const ret = wasm.appendSessionEntry(handle, entry);
10
+ return ret;
11
+ }
12
+
3
13
  /**
4
14
  * @param {AgentOptions} options
5
15
  * @returns {CreateAgentResult}
@@ -19,13 +29,34 @@ export function destroyAgent(handle) {
19
29
  }
20
30
 
21
31
  /**
22
- * @returns {string[]}
32
+ * @param {EstimateTokensInput} input
33
+ * @returns {EstimateTokensResult}
23
34
  */
24
- export function drainTraceLog() {
25
- const ret = wasm.drainTraceLog();
26
- var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
27
- wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
28
- return v1;
35
+ export function estimateTokens(input) {
36
+ const ret = wasm.estimateTokens(input);
37
+ return ret;
38
+ }
39
+
40
+ /**
41
+ * @param {string} text
42
+ * @returns {EstimateTokensResult}
43
+ */
44
+ export function estimateTokensForText(text) {
45
+ const ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
46
+ const len0 = WASM_VECTOR_LEN;
47
+ const ret = wasm.estimateTokensForText(ptr0, len0);
48
+ return ret;
49
+ }
50
+
51
+ /**
52
+ * @param {string} tool_name
53
+ * @returns {ContextStrategy}
54
+ */
55
+ export function fallbackStrategy(tool_name) {
56
+ const ptr0 = passStringToWasm0(tool_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
57
+ const len0 = WASM_VECTOR_LEN;
58
+ const ret = wasm.fallbackStrategy(ptr0, len0);
59
+ return ret;
29
60
  }
30
61
 
31
62
  /**
@@ -48,6 +79,37 @@ export function followUp(handle, message) {
48
79
  return ret;
49
80
  }
50
81
 
82
+ /**
83
+ * @param {number} handle
84
+ * @returns {SessionBranchResult}
85
+ */
86
+ export function getSessionBranch(handle) {
87
+ const ret = wasm.getSessionBranch(handle);
88
+ return ret;
89
+ }
90
+
91
+ /**
92
+ * @param {number} handle
93
+ * @returns {SessionStateResult}
94
+ */
95
+ export function getSessionState(handle) {
96
+ const ret = wasm.getSessionState(handle);
97
+ return ret;
98
+ }
99
+
100
+ /**
101
+ * @param {number} handle
102
+ * @param {string} target_id
103
+ * @param {BranchSummary | null} [summary]
104
+ * @returns {MoveToResult}
105
+ */
106
+ export function moveTo(handle, target_id, summary) {
107
+ const ptr0 = passStringToWasm0(target_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
108
+ const len0 = WASM_VECTOR_LEN;
109
+ const ret = wasm.moveTo(handle, ptr0, len0, isLikeNone(summary) ? 0 : addToExternrefTable0(summary));
110
+ return ret;
111
+ }
112
+
51
113
  /**
52
114
  * @param {number} handle
53
115
  * @param {LlmResult} result
@@ -134,6 +196,25 @@ export function reset(handle) {
134
196
  return ret;
135
197
  }
136
198
 
199
+ /**
200
+ * @param {string} level
201
+ */
202
+ export function setLogLevel(level) {
203
+ const ptr0 = passStringToWasm0(level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
204
+ const len0 = WASM_VECTOR_LEN;
205
+ wasm.setLogLevel(ptr0, len0);
206
+ }
207
+
208
+ /**
209
+ * @param {number} handle
210
+ * @param {SessionState} state
211
+ * @returns {EmptyResult}
212
+ */
213
+ export function setSessionState(handle, state) {
214
+ const ret = wasm.setSessionState(handle, state);
215
+ return ret;
216
+ }
217
+
137
218
  /**
138
219
  * @param {number} handle
139
220
  * @returns {StateResult}
@@ -181,6 +262,47 @@ function __wbg_get_imports() {
181
262
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
182
263
  }
183
264
  },
265
+ __wbg_log_0c201ade58bb55e1: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
266
+ let deferred0_0;
267
+ let deferred0_1;
268
+ try {
269
+ deferred0_0 = arg0;
270
+ deferred0_1 = arg1;
271
+ console.log(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3), getStringFromWasm0(arg4, arg5), getStringFromWasm0(arg6, arg7));
272
+ } finally {
273
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
274
+ }
275
+ },
276
+ __wbg_log_ce2c4456b290c5e7: function(arg0, arg1) {
277
+ let deferred0_0;
278
+ let deferred0_1;
279
+ try {
280
+ deferred0_0 = arg0;
281
+ deferred0_1 = arg1;
282
+ console.log(getStringFromWasm0(arg0, arg1));
283
+ } finally {
284
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
285
+ }
286
+ },
287
+ __wbg_mark_b4d943f3bc2d2404: function(arg0, arg1) {
288
+ performance.mark(getStringFromWasm0(arg0, arg1));
289
+ },
290
+ __wbg_measure_84362959e621a2c1: function() { return handleError(function (arg0, arg1, arg2, arg3) {
291
+ let deferred0_0;
292
+ let deferred0_1;
293
+ let deferred1_0;
294
+ let deferred1_1;
295
+ try {
296
+ deferred0_0 = arg0;
297
+ deferred0_1 = arg1;
298
+ deferred1_0 = arg2;
299
+ deferred1_1 = arg3;
300
+ performance.measure(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
301
+ } finally {
302
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
303
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
304
+ }
305
+ }, arguments); },
184
306
  __wbg_new_227d7c05414eb861: function() {
185
307
  const ret = new Error();
186
308
  return ret;
@@ -200,11 +322,6 @@ function __wbg_get_imports() {
200
322
  const ret = JSON.stringify(arg0);
201
323
  return ret;
202
324
  }, arguments); },
203
- __wbindgen_cast_0000000000000001: function(arg0, arg1) {
204
- // Cast intrinsic for `Ref(String) -> Externref`.
205
- const ret = getStringFromWasm0(arg0, arg1);
206
- return ret;
207
- },
208
325
  __wbindgen_init_externref_table: function() {
209
326
  const table = wasm.__wbindgen_externrefs;
210
327
  const offset = table.grow(4);
@@ -227,17 +344,6 @@ function addToExternrefTable0(obj) {
227
344
  return idx;
228
345
  }
229
346
 
230
- function getArrayJsValueFromWasm0(ptr, len) {
231
- ptr = ptr >>> 0;
232
- const mem = getDataViewMemory0();
233
- const result = [];
234
- for (let i = ptr; i < ptr + 4 * len; i += 4) {
235
- result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true)));
236
- }
237
- wasm.__externref_drop_slice(ptr, len);
238
- return result;
239
- }
240
-
241
347
  let cachedDataViewMemory0 = null;
242
348
  function getDataViewMemory0() {
243
349
  if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
Binary file
package/sdk/index.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * High-level JS SDK for @pi-oxide/pi-host-web.
3
+ *
4
+ * Re-exports all raw types so consumers never need to import from ./raw.
5
+ */
6
+
7
+ export * from "../pi_host_web.js";
8
+
9
+ export declare function ensureInit(): Promise<void>;
10
+
11
+ export declare function toolResult(
12
+ text: string,
13
+ opts?: { terminate?: boolean }
14
+ ): { content: Array<{ type: "text"; text: string }>; terminate?: boolean };
15
+
16
+ export declare function toolError(
17
+ code: string,
18
+ message: string
19
+ ): { error: { code: string; message: string } };
20
+
21
+ export interface LlmStream {
22
+ chunks: AsyncIterable<LlmChunk>;
23
+ result: Promise<LlmResult>;
24
+ }
25
+
26
+ export interface LlmProvider {
27
+ call(context: LlmContext): Promise<LlmStream> | LlmStream;
28
+ }
29
+
30
+ export type ToolMap = Record<
31
+ string,
32
+ (call: ToolCall) => Promise<ToolResult> | ToolResult
33
+ >;
34
+
35
+ export interface AgentRunConfig {
36
+ llm: LlmProvider;
37
+ tools: ToolMap;
38
+ onEvent?: (event: AgentEvent) => void;
39
+ }
40
+
41
+ export declare class Agent {
42
+ static create(options: AgentOptions): Promise<Agent>;
43
+ run(promptText: string, config: AgentRunConfig): Promise<AgentAction>;
44
+ reset(): void;
45
+ state(): AgentState;
46
+ getSessionState(): SessionState;
47
+ setSessionState(sessionState: SessionState): void;
48
+ steer(message: AgentMessage): AgentEvent[];
49
+ followUp(message: AgentMessage): void;
50
+ destroy(): void;
51
+ }
package/sdk/index.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * High-level JS SDK for @pi-oxide/pi-host-web.
3
+ *
4
+ * Hides WASM loading, numeric handles, and the agent drive-loop.
5
+ * Supports streaming LLM responses and full agent lifecycle.
6
+ *
7
+ * Import from the package root:
8
+ * import { Agent, toolResult } from "@pi-oxide/pi-host-web";
9
+ */
10
+
11
+ import {
12
+ createAgent,
13
+ destroyAgent,
14
+ feedLlmChunk,
15
+ followUp,
16
+ getSessionState,
17
+ initSync,
18
+ onLlmDone,
19
+ onToolCancelled,
20
+ onToolDone,
21
+ onToolStarted,
22
+ projectContext,
23
+ prompt,
24
+ reset,
25
+ setSessionState,
26
+ state,
27
+ steer,
28
+ default as init,
29
+ } from "../pi_host_web.js";
30
+
31
+ export { projectContext };
32
+
33
+ let initialized = false;
34
+
35
+ /** Ensure the WASM module is loaded. Safe to call multiple times. */
36
+ export async function ensureInit() {
37
+ if (initialized) return;
38
+ if (typeof window !== "undefined") {
39
+ await init();
40
+ } else {
41
+ const { readFileSync } = await import("node:fs");
42
+ const bytes = readFileSync(
43
+ new URL("../pi_host_web_bg.wasm", import.meta.url)
44
+ );
45
+ initSync({ module: bytes });
46
+ }
47
+ initialized = true;
48
+ }
49
+
50
+ class HostError extends Error {
51
+ constructor(code, message) {
52
+ super(message);
53
+ this.code = code;
54
+ this.name = "HostError";
55
+ }
56
+ }
57
+
58
+ function unwrap(result) {
59
+ if (!result.ok) {
60
+ throw new HostError(result.error.code, result.error.message);
61
+ }
62
+ return result.data;
63
+ }
64
+
65
+ /** Build a successful tool result payload. */
66
+ export function toolResult(text, opts = {}) {
67
+ const payload = {
68
+ content: [{ type: "text", text }],
69
+ };
70
+ if (opts.terminate) {
71
+ payload.terminate = true;
72
+ }
73
+ return payload;
74
+ }
75
+
76
+ /** Build an error tool result payload. */
77
+ export function toolError(code, message) {
78
+ return { error: { code, message } };
79
+ }
80
+
81
+ /**
82
+ * High-level agent that manages the WASM handle and drive-loop.
83
+ *
84
+ * Usage:
85
+ * const agent = await Agent.create(options);
86
+ * const finalAction = await agent.run("hello", { llm, tools, onEvent });
87
+ * agent.destroy();
88
+ */
89
+ export class Agent {
90
+ /** @type {number} */
91
+ #handle;
92
+
93
+ constructor(handle) {
94
+ this.#handle = handle;
95
+ }
96
+
97
+ /** Create a new agent. Loads WASM on first call automatically. */
98
+ static async create(options) {
99
+ await ensureInit();
100
+ const result = unwrap(createAgent(options));
101
+ return new Agent(result.handle);
102
+ }
103
+
104
+ /**
105
+ * Run one user prompt through the full turn loop.
106
+ *
107
+ * @param {string} promptText
108
+ * @param {object} config
109
+ * @param {LlmProvider} config.llm
110
+ * @param {Record<string, (call: ToolCall) => Promise<ToolResult> | ToolResult>} config.tools
111
+ * @param {(event: AgentEvent) => void} [config.onEvent]
112
+ * @returns {Promise<AgentAction>} terminal action (finished or wait_for_input)
113
+ */
114
+ async run(promptText, config) {
115
+ let step = unwrap(prompt(this.#handle, { text: promptText }));
116
+ for (const event of step.events) {
117
+ config.onEvent?.(event);
118
+ }
119
+
120
+ while (true) {
121
+ let actions = step.actions ?? [];
122
+ if (actions.length === 0) {
123
+ return { type: "finished", messages: [] };
124
+ }
125
+
126
+ for (const action of actions) {
127
+ switch (action.type) {
128
+ case "stream_llm": {
129
+ const stream = await config.llm.call(action.context);
130
+ for await (const chunk of stream.chunks) {
131
+ const ev = unwrap(feedLlmChunk(this.#handle, chunk));
132
+ for (const e of ev.events) config.onEvent?.(e);
133
+ }
134
+ const result = await stream.result;
135
+ step = unwrap(onLlmDone(this.#handle, result));
136
+ for (const e of step.events) config.onEvent?.(e);
137
+ break;
138
+ }
139
+
140
+ case "execute_tools": {
141
+ for (const call of action.calls) {
142
+ const started = unwrap(onToolStarted(this.#handle, call.id));
143
+ for (const e of started.events) config.onEvent?.(e);
144
+
145
+ const handler = config.tools[call.name];
146
+ let result;
147
+ if (handler) {
148
+ result = await handler(call);
149
+ } else {
150
+ result = toolError("unknown_tool", `No handler for ${call.name}`);
151
+ }
152
+ step = unwrap(onToolDone(this.#handle, call.id, result));
153
+ for (const e of step.events) config.onEvent?.(e);
154
+ }
155
+ break;
156
+ }
157
+
158
+ case "cancel_tools": {
159
+ for (const id of action.tool_call_ids) {
160
+ step = unwrap(
161
+ onToolCancelled(this.#handle, id, action.reason)
162
+ );
163
+ for (const e of step.events) config.onEvent?.(e);
164
+ }
165
+ break;
166
+ }
167
+
168
+ case "finished":
169
+ return action;
170
+
171
+ case "wait_for_input":
172
+ return action;
173
+
174
+ default:
175
+ return action;
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ /** Reset agent state (clear messages, return to idle). */
182
+ reset() {
183
+ unwrap(reset(this.#handle));
184
+ }
185
+
186
+ /** Get public agent state. */
187
+ state() {
188
+ return unwrap(state(this.#handle));
189
+ }
190
+
191
+ /** Get session state for persistence. */
192
+ getSessionState() {
193
+ return unwrap(getSessionState(this.#handle));
194
+ }
195
+
196
+ /** Set session state (e.g. after restoring from storage). */
197
+ setSessionState(sessionState) {
198
+ unwrap(setSessionState(this.#handle, sessionState));
199
+ }
200
+
201
+ /** Send a steering message mid-turn. */
202
+ steer(message) {
203
+ const out = unwrap(steer(this.#handle, message));
204
+ return out.events;
205
+ }
206
+
207
+ /** Queue a follow-up message. */
208
+ followUp(message) {
209
+ unwrap(followUp(this.#handle, message));
210
+ }
211
+
212
+ /** Destroy the underlying WASM handle. */
213
+ destroy() {
214
+ destroyAgent(this.#handle);
215
+ }
216
+ }