@pi-oxide/pi-host-web 0.3.0 → 0.4.0

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/sdk/agent.ts ADDED
@@ -0,0 +1,274 @@
1
+ // Public Agent class — the primary SDK surface.
2
+ // Thin facade over the internal engine. No WASM imports here.
3
+
4
+ import { EventEmitter } from "./events.ts";
5
+ import type {
6
+ AgentConfig,
7
+ AgentInput,
8
+ AgentRunOptions,
9
+ AgentRunResult,
10
+ AgentEventName,
11
+ AgentEventHandler,
12
+ AgentStatus,
13
+ Unsubscribe,
14
+ AgentError,
15
+ } from "./types.ts";
16
+ import { createAgentError } from "./errors.ts";
17
+ import type { HostAgent } from "./internal/engine.ts";
18
+ import {
19
+ runAgentTurn,
20
+ resetAgentState,
21
+ steerAgent,
22
+ createEngineAgent,
23
+ destroyEngineAgent,
24
+ } from "./internal/engine.ts";
25
+
26
+ export class Agent {
27
+ private config: AgentConfig;
28
+ private emitter: EventEmitter;
29
+ private status: AgentStatus = { state: "idle" };
30
+ private currentRun: Promise<AgentRunResult> | null = null;
31
+ private currentAbortController: AbortController | null = null;
32
+ private disposed = false;
33
+ private engineAgent: HostAgent | null = null;
34
+
35
+ constructor(config: AgentConfig) {
36
+ this.config = config;
37
+ this.emitter = new EventEmitter();
38
+ }
39
+
40
+ on<E extends AgentEventName>(
41
+ event: E,
42
+ handler: AgentEventHandler<E>,
43
+ ): Unsubscribe {
44
+ if (this.disposed) {
45
+ return () => {};
46
+ }
47
+ return this.emitter.on(event, handler);
48
+ }
49
+
50
+ async run(
51
+ input: string | AgentInput,
52
+ options?: AgentRunOptions,
53
+ ): Promise<AgentRunResult> {
54
+ if (this.disposed) {
55
+ const error = createAgentError(
56
+ "agent_disposed",
57
+ "Agent has been disposed",
58
+ { recoverable: false },
59
+ );
60
+ const result: AgentRunResult = {
61
+ status: "failed",
62
+ text: "",
63
+ toolCalls: [],
64
+ artifacts: [],
65
+ error,
66
+ };
67
+ this.emitter.emit("error", error);
68
+ this.emitter.emit("status", { state: "failed", message: error.message });
69
+ this.emitter.emit("done", result);
70
+ return result;
71
+ }
72
+
73
+ if (this.currentRun) {
74
+ const error = createAgentError(
75
+ "agent_busy",
76
+ "Agent is already running a turn",
77
+ { recoverable: true },
78
+ );
79
+ const result: AgentRunResult = {
80
+ status: "failed",
81
+ text: "",
82
+ toolCalls: [],
83
+ artifacts: [],
84
+ error,
85
+ };
86
+ this.emitter.emit("error", error);
87
+ this.emitter.emit("status", { state: "failed", message: error.message });
88
+ this.emitter.emit("done", result);
89
+ return result;
90
+ }
91
+
92
+ const abortController = new AbortController();
93
+ this.currentAbortController = abortController;
94
+
95
+ // Merge external signal if provided
96
+ if (options?.signal) {
97
+ if (options.signal.aborted) {
98
+ abortController.abort(options.signal.reason);
99
+ } else {
100
+ options.signal.addEventListener(
101
+ "abort",
102
+ () => {
103
+ abortController.abort(options.signal!.reason);
104
+ },
105
+ { once: true },
106
+ );
107
+ }
108
+ }
109
+
110
+ const runPromise = this._doRun(input, options, abortController.signal);
111
+ this.currentRun = runPromise;
112
+
113
+ try {
114
+ const result = await runPromise;
115
+ this.emitter.emit("done", result);
116
+ return result;
117
+ } catch (e) {
118
+ // Safety net: convert any unexpected throw to a failed result
119
+ const error = createAgentError(
120
+ "internal_error",
121
+ e instanceof Error ? e.message : String(e),
122
+ { cause: e, recoverable: false },
123
+ );
124
+ const failedResult: AgentRunResult = {
125
+ status: "failed",
126
+ text: "",
127
+ toolCalls: [],
128
+ artifacts: [],
129
+ error,
130
+ };
131
+ this.emitter.emit("error", error);
132
+ this.emitter.emit("status", { state: "failed", message: error.message });
133
+ this.emitter.emit("done", failedResult);
134
+ return failedResult;
135
+ } finally {
136
+ this.currentRun = null;
137
+ this.currentAbortController = null;
138
+ }
139
+ }
140
+
141
+ private async _doRun(
142
+ input: string | AgentInput,
143
+ options: AgentRunOptions | undefined,
144
+ signal: AbortSignal,
145
+ ): Promise<AgentRunResult> {
146
+ // Lazy initialization on first run
147
+ if (!this.engineAgent) {
148
+ this.engineAgent = await createEngineAgent(this.config, {
149
+ onEvent: (event) => this.emitter.emit(event.type as AgentEventName, event.payload),
150
+ onStatus: (status) => {
151
+ this.status = status;
152
+ this.emitter.emit("status", status);
153
+ },
154
+ });
155
+ }
156
+
157
+ try {
158
+ return await runAgentTurn(
159
+ this.engineAgent,
160
+ this.config,
161
+ input,
162
+ options,
163
+ signal,
164
+ {
165
+ onEvent: (event) =>
166
+ this.emitter.emit(event.type as AgentEventName, event.payload),
167
+ onStatus: (status) => {
168
+ this.status = status;
169
+ this.emitter.emit("status", status);
170
+ },
171
+ },
172
+ );
173
+ } catch (e) {
174
+ const isAbort =
175
+ signal.aborted ||
176
+ (e instanceof Error && e.name === "AbortError") ||
177
+ (e instanceof Error && e.message.includes("user_aborted"));
178
+
179
+ if (isAbort) {
180
+ const abortedResult: AgentRunResult = {
181
+ status: "aborted",
182
+ text: "",
183
+ toolCalls: [],
184
+ artifacts: [],
185
+ };
186
+ this.emitter.emit("status", {
187
+ state: "aborted",
188
+ message: "Stopped by user",
189
+ });
190
+ return abortedResult;
191
+ }
192
+
193
+ const code =
194
+ e instanceof Error && "code" in e && typeof (e as { code: unknown }).code === "string"
195
+ ? ((e as { code: string }).code as AgentError["code"])
196
+ : "internal_error";
197
+ const error = createAgentError(
198
+ code,
199
+ e instanceof Error ? e.message : String(e),
200
+ { cause: e, recoverable: false },
201
+ );
202
+ const failedResult: AgentRunResult = {
203
+ status: "failed",
204
+ text: "",
205
+ toolCalls: [],
206
+ artifacts: [],
207
+ error,
208
+ };
209
+ this.emitter.emit("error", error);
210
+ this.emitter.emit("status", { state: "failed", message: error.message });
211
+ return failedResult;
212
+ }
213
+ }
214
+
215
+ stop(reason?: string): void {
216
+ if (this.disposed || !this.currentAbortController) return;
217
+ this.currentAbortController.abort(reason ?? "user-requested");
218
+ }
219
+
220
+ async steer(input: string | AgentInput): Promise<void> {
221
+ if (this.disposed) {
222
+ throw createAgentError(
223
+ "agent_disposed",
224
+ "Agent has been disposed",
225
+ { recoverable: false },
226
+ );
227
+ }
228
+ if (!this.engineAgent) {
229
+ throw createAgentError(
230
+ "agent_not_initialized",
231
+ "Agent has not been run yet",
232
+ { recoverable: true },
233
+ );
234
+ }
235
+ return steerAgent(this.engineAgent, input);
236
+ }
237
+
238
+ async reset(): Promise<void> {
239
+ if (this.disposed) {
240
+ throw createAgentError(
241
+ "agent_disposed",
242
+ "Agent has been disposed",
243
+ { recoverable: false },
244
+ );
245
+ }
246
+ if (this.engineAgent) {
247
+ await resetAgentState(this.engineAgent);
248
+ this.engineAgent = null;
249
+ }
250
+ this.currentRun = null;
251
+ this.currentAbortController = null;
252
+ this.status = { state: "idle" };
253
+ this.emitter.emit("status", this.status);
254
+ }
255
+
256
+ dispose(): void {
257
+ if (this.disposed) return;
258
+ this.disposed = true;
259
+ if (this.currentAbortController) {
260
+ this.currentAbortController.abort("disposed");
261
+ this.currentAbortController = null;
262
+ }
263
+ if (this.engineAgent) {
264
+ destroyEngineAgent(this.engineAgent);
265
+ this.engineAgent = null;
266
+ }
267
+ this.emitter.clear();
268
+ this.currentRun = null;
269
+ }
270
+
271
+ getStatus(): AgentStatus {
272
+ return this.status;
273
+ }
274
+ }
@@ -0,0 +1,35 @@
1
+ // Artifact types for the pi-oxide SDK.
2
+ // These are public objects, not leaked host internals.
3
+
4
+ export interface AgentArtifact {
5
+ id: string;
6
+ kind: "text" | "json" | "binary";
7
+ content: string | Uint8Array | unknown;
8
+ mimeType?: string;
9
+ title?: string;
10
+ metadata?: Record<string, unknown>;
11
+ createdAt: number;
12
+ }
13
+
14
+ export interface AgentArtifactRef {
15
+ id: string;
16
+ kind: AgentArtifact["kind"];
17
+ title?: string;
18
+ mimeType?: string;
19
+ }
20
+
21
+ export interface ArtifactPolicy {
22
+ mode: "inline" | "external";
23
+ }
24
+
25
+ export interface ArtifactSearchQuery {
26
+ text: string;
27
+ limit?: number;
28
+ }
29
+
30
+ export interface ArtifactSearchResult {
31
+ artifact: AgentArtifactRef;
32
+ snippet?: string;
33
+ score?: number;
34
+ matchCount?: number;
35
+ }
package/sdk/context.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Context policy types for the pi-oxide SDK.
2
+ // Re-exported from types.ts for a cleaner public API surface.
3
+
4
+ export type { AgentContextPolicy, AgentSummarizer } from "./types.ts";
package/sdk/errors.ts ADDED
@@ -0,0 +1,24 @@
1
+ // AgentError interface and factory.
2
+ // AgentError is an interface, NOT a class — it is a plain object.
3
+
4
+ import type { AgentError } from "./types.ts";
5
+
6
+ export type { AgentError } from "./types.ts";
7
+
8
+ export function createAgentError(
9
+ code: AgentError["code"],
10
+ message: string,
11
+ options?: {
12
+ cause?: unknown;
13
+ recoverable?: boolean;
14
+ metadata?: Record<string, unknown>;
15
+ },
16
+ ): AgentError {
17
+ return {
18
+ code,
19
+ message,
20
+ cause: options?.cause,
21
+ recoverable: options?.recoverable ?? false,
22
+ metadata: options?.metadata,
23
+ };
24
+ }
package/sdk/events.ts ADDED
@@ -0,0 +1,52 @@
1
+ // Lightweight, type-safe EventEmitter (not Node's).
2
+ // Maps event names to typed handlers using the SDK's AgentEventName union.
3
+
4
+ import type { AgentEventName, AgentEventHandler } from "./types.ts";
5
+
6
+ type AnyHandler = (payload: unknown) => void;
7
+
8
+ export class EventEmitter {
9
+ private handlers: Partial<Record<AgentEventName, Set<AnyHandler>>> = {};
10
+
11
+ on<E extends AgentEventName>(
12
+ event: E,
13
+ handler: AgentEventHandler<E>,
14
+ ): () => void {
15
+ const set = (this.handlers[event] ??= new Set()) as Set<AnyHandler>;
16
+ set.add(handler as AnyHandler);
17
+
18
+ return () => {
19
+ set.delete(handler as AnyHandler);
20
+ if (set.size === 0) {
21
+ delete this.handlers[event];
22
+ }
23
+ };
24
+ }
25
+
26
+ off<E extends AgentEventName>(
27
+ event: E,
28
+ handler: AgentEventHandler<E>,
29
+ ): void {
30
+ const set = this.handlers[event];
31
+ if (!set) return;
32
+ set.delete(handler as AnyHandler);
33
+ if (set.size === 0) {
34
+ delete this.handlers[event];
35
+ }
36
+ }
37
+
38
+ emit<E extends AgentEventName>(
39
+ event: E,
40
+ payload: Parameters<AgentEventHandler<E>>[0],
41
+ ): void {
42
+ const set = this.handlers[event];
43
+ if (!set) return;
44
+ for (const handler of set) {
45
+ handler(payload);
46
+ }
47
+ }
48
+
49
+ clear(): void {
50
+ this.handlers = {};
51
+ }
52
+ }
package/sdk/index.d.ts CHANGED
@@ -4,48 +4,26 @@
4
4
  * Re-exports all raw types so consumers never need to import from ./raw.
5
5
  */
6
6
 
7
- export * from "../pi_host_web.js";
7
+ export * from "../pi_host_web";
8
8
 
9
9
  export declare function ensureInit(): Promise<void>;
10
10
 
11
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;
12
+ text: string,
13
+ opts?: { terminate?: boolean; details?: object },
14
+ ): {
15
+ content: Array<{ type: "text"; text: string }>;
16
+ terminate?: boolean;
17
+ details?: object;
18
+ };
19
+
20
+ export declare class HostError extends Error {
21
+ code: string;
22
+ constructor(code: string, message: string);
28
23
  }
29
24
 
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
- }
25
+ export declare function unwrap<T>(result: {
26
+ ok: boolean;
27
+ data?: T;
28
+ error?: { code: string; message: string };
29
+ }): T;