@pi-oxide/pi-host-web 0.4.0 → 0.7.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/dist/index.d.ts +2 -0
- package/dist/index.js +2535 -0
- package/dist/sdk/agent.d.ts +21 -0
- package/dist/sdk/agent.d.ts.map +1 -0
- package/dist/sdk/artifacts.d.ts +29 -0
- package/dist/sdk/artifacts.d.ts.map +1 -0
- package/dist/sdk/context.d.ts +2 -0
- package/dist/sdk/context.d.ts.map +1 -0
- package/dist/sdk/errors.d.ts +8 -0
- package/dist/sdk/errors.d.ts.map +1 -0
- package/dist/sdk/events.d.ts +9 -0
- package/dist/sdk/events.d.ts.map +1 -0
- package/dist/sdk/index.d.ts +16 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/init.d.ts +30 -0
- package/dist/sdk/init.d.ts.map +1 -0
- package/dist/sdk/internal/engine.d.ts +91 -0
- package/dist/sdk/internal/engine.d.ts.map +1 -0
- package/dist/sdk/internal/events.d.ts +30 -0
- package/dist/sdk/internal/events.d.ts.map +1 -0
- package/dist/sdk/internal/logger.d.ts +51 -0
- package/dist/sdk/internal/logger.d.ts.map +1 -0
- package/dist/sdk/internal/providers/anthropic.d.ts +75 -0
- package/dist/sdk/internal/providers/anthropic.d.ts.map +1 -0
- package/dist/sdk/internal/providers/openai.d.ts +13 -0
- package/dist/sdk/internal/providers/openai.d.ts.map +1 -0
- package/dist/sdk/internal/providers/types.d.ts +57 -0
- package/dist/sdk/internal/providers/types.d.ts.map +1 -0
- package/dist/sdk/internal/stores/indexedDb.d.ts +3 -0
- package/dist/sdk/internal/stores/indexedDb.d.ts.map +1 -0
- package/dist/sdk/internal/stores/persistence.d.ts +13 -0
- package/dist/sdk/internal/stores/persistence.d.ts.map +1 -0
- package/dist/sdk/internal/tools/artifact.d.ts +3 -0
- package/dist/sdk/internal/tools/artifact.d.ts.map +1 -0
- package/dist/sdk/internal/tools/browser.d.ts +19 -0
- package/dist/sdk/internal/tools/browser.d.ts.map +1 -0
- package/dist/sdk/internal/tools/browserRuntime.d.ts +47 -0
- package/dist/sdk/internal/tools/browserRuntime.d.ts.map +1 -0
- package/dist/sdk/internal/tools/liveRuntime.d.ts +12 -0
- package/dist/sdk/internal/tools/liveRuntime.d.ts.map +1 -0
- package/dist/sdk/internal/tools/registry.d.ts +17 -0
- package/dist/sdk/internal/tools/registry.d.ts.map +1 -0
- package/dist/sdk/internal/tools/service.d.ts +15 -0
- package/dist/sdk/internal/tools/service.d.ts.map +1 -0
- package/dist/sdk/internal/util/types.d.ts +6 -0
- package/dist/sdk/internal/util/types.d.ts.map +1 -0
- package/dist/sdk/model.d.ts +16 -0
- package/dist/sdk/model.d.ts.map +1 -0
- package/dist/sdk/snapshot.d.ts +7 -0
- package/dist/sdk/snapshot.d.ts.map +1 -0
- package/dist/sdk/stores.d.ts +22 -0
- package/dist/sdk/stores.d.ts.map +1 -0
- package/dist/sdk/tools.d.ts +32 -0
- package/dist/sdk/tools.d.ts.map +1 -0
- package/dist/sdk/types.d.ts +200 -0
- package/dist/sdk/types.d.ts.map +1 -0
- package/package.json +69 -39
- package/pi_host_web.d.ts +4 -1
- package/pi_host_web.js +11 -0
- package/pi_host_web_bg.wasm +0 -0
- package/pi_host_web_bg.wasm.d.ts +36 -0
- package/sdk/agent.ts +0 -274
- package/sdk/artifacts.ts +0 -35
- package/sdk/context.ts +0 -4
- package/sdk/errors.ts +0 -24
- package/sdk/events.ts +0 -52
- package/sdk/index.d.ts +0 -29
- package/sdk/index.js +0 -111
- package/sdk/index.ts +0 -53
- package/sdk/init.ts +0 -58
- package/sdk/internal/engine.ts +0 -614
- package/sdk/internal/events.ts +0 -241
- package/sdk/internal/providers/anthropic.ts +0 -440
- package/sdk/internal/providers/openai.ts +0 -177
- package/sdk/internal/providers/types.ts +0 -64
- package/sdk/internal/stores/indexedDb.ts +0 -24
- package/sdk/internal/stores/persistence.ts +0 -71
- package/sdk/internal/tools/artifact.ts +0 -24
- package/sdk/internal/tools/browser.ts +0 -449
- package/sdk/internal/tools/browserRuntime.ts +0 -48
- package/sdk/internal/tools/liveRuntime.ts +0 -151
- package/sdk/internal/tools/registry.ts +0 -174
- package/sdk/internal/tools/service.ts +0 -157
- package/sdk/model.ts +0 -35
- package/sdk/react/index.ts +0 -1
- package/sdk/react/useAgent.ts +0 -334
- package/sdk/snapshot.ts +0 -25
- package/sdk/stores.ts +0 -72
- package/sdk/tools.ts +0 -47
- package/sdk/types.ts +0 -252
package/sdk/internal/engine.ts
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
// Engine orchestration — the only layer that knows about HostAgent, PersistData, and directives.
|
|
2
|
-
// Wires all AgentConfig fields (instructions, context, artifacts, model).
|
|
3
|
-
// Uses the raw WASM API directly (createHostAgent, startTurn, hostFeedLlmChunk, etc.).
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
type AgentEvent as RawAgentEvent,
|
|
7
|
-
type AgentMessage as WasmAgentMessage,
|
|
8
|
-
type Content,
|
|
9
|
-
type LlmChunk,
|
|
10
|
-
type LlmResult,
|
|
11
|
-
type PersistData,
|
|
12
|
-
type LlmContext,
|
|
13
|
-
type ToolCall,
|
|
14
|
-
type ToolResult,
|
|
15
|
-
type ToolDefinition,
|
|
16
|
-
type CancelReason,
|
|
17
|
-
createHostAgent,
|
|
18
|
-
destroyHostAgent,
|
|
19
|
-
startTurn,
|
|
20
|
-
hostFeedLlmChunk,
|
|
21
|
-
hostLlmDone,
|
|
22
|
-
hostToolDone,
|
|
23
|
-
hostToolCancelled,
|
|
24
|
-
hostContinueTurn,
|
|
25
|
-
hostAbort,
|
|
26
|
-
hostAcceptCompaction,
|
|
27
|
-
hostSteer,
|
|
28
|
-
hostReset,
|
|
29
|
-
getHostAgentPersistData,
|
|
30
|
-
restoreHostAgent,
|
|
31
|
-
} from "../../pi_host_web.js";
|
|
32
|
-
import { ensureInit, HostError } from "../init.ts";
|
|
33
|
-
import { EventMapper } from "./events.ts";
|
|
34
|
-
import { SnapshotSerializer } from "../snapshot.ts";
|
|
35
|
-
import { ToolRegistryBuilder } from "./tools/registry.ts";
|
|
36
|
-
import type {
|
|
37
|
-
AgentConfig,
|
|
38
|
-
AgentInput,
|
|
39
|
-
AgentRunOptions,
|
|
40
|
-
AgentRunResult,
|
|
41
|
-
AgentStatus,
|
|
42
|
-
AgentMessage,
|
|
43
|
-
ModelResponse,
|
|
44
|
-
TokenUsage,
|
|
45
|
-
} from "../types.ts";
|
|
46
|
-
import { createAgentError } from "../errors.ts";
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Types
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
export interface ArtifactStore {
|
|
53
|
-
save(sessionId: string, artifactId: string, content: string): Promise<void>;
|
|
54
|
-
load(sessionId: string, artifactId: string): Promise<string | null>;
|
|
55
|
-
search(sessionId: string, query: string): Promise<Array<{ id: string; snippet: string; match_count: number }>>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface AgentRunConfig {
|
|
59
|
-
llm: {
|
|
60
|
-
call(context: LlmContext, signal?: AbortSignal): Promise<LlmStream> | LlmStream;
|
|
61
|
-
summarize?(messages: WasmAgentMessage[], signal?: AbortSignal): Promise<string>;
|
|
62
|
-
};
|
|
63
|
-
tools: Record<string, (call: ToolCall) => Promise<ToolResult> | ToolResult>;
|
|
64
|
-
llmTools?: ToolDefinition[];
|
|
65
|
-
onEvent?: (event: RawAgentEvent) => void;
|
|
66
|
-
signal?: AbortSignal;
|
|
67
|
-
onPersist?: (data: PersistData) => Promise<void>;
|
|
68
|
-
artifactStore?: ArtifactStore;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface LlmStream {
|
|
72
|
-
chunks: AsyncIterable<LlmChunk>;
|
|
73
|
-
result: Promise<LlmResult>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface TurnResult {
|
|
77
|
-
aborted: boolean;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// HostAgent — thin wrapper around the WASM handle
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
export class HostAgent {
|
|
85
|
-
/** @internal */
|
|
86
|
-
readonly handle: number;
|
|
87
|
-
readonly sessionId: string | undefined;
|
|
88
|
-
|
|
89
|
-
constructor(handle: number, sessionId?: string) {
|
|
90
|
-
this.handle = handle;
|
|
91
|
-
this.sessionId = sessionId;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
getSessionId(): string | undefined {
|
|
95
|
-
return this.sessionId;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
steer(message: WasmAgentMessage): { events: RawAgentEvent[] } {
|
|
99
|
-
const result = unwrap(hostSteer(this.handle, message));
|
|
100
|
-
return { events: result.events };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
getPersistData(): PersistData {
|
|
104
|
-
const result = unwrap(getHostAgentPersistData(this.handle));
|
|
105
|
-
return result.state;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
destroy() {
|
|
109
|
-
destroyHostAgent(this.handle);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
// Host agent lifecycle
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
|
|
117
|
-
export async function createHostAgentInstance(
|
|
118
|
-
config: AgentConfig,
|
|
119
|
-
sessionState?: PersistData,
|
|
120
|
-
): Promise<HostAgent> {
|
|
121
|
-
await ensureInit();
|
|
122
|
-
const options = {
|
|
123
|
-
system_prompt: config.instructions ?? "You are a helpful assistant.",
|
|
124
|
-
model: buildModelOptions(config.model),
|
|
125
|
-
session_id: config.sessionId,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
let handle: number;
|
|
129
|
-
if (sessionState) {
|
|
130
|
-
const restored = unwrap(restoreHostAgent(options, sessionState));
|
|
131
|
-
handle = restored.handle;
|
|
132
|
-
} else {
|
|
133
|
-
const result = unwrap(createHostAgent(options, buildContextBudget(config.context)));
|
|
134
|
-
handle = result.handle;
|
|
135
|
-
}
|
|
136
|
-
return new HostAgent(handle, config.sessionId);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export async function runTurnWithHostAgent(
|
|
140
|
-
hostAgent: HostAgent,
|
|
141
|
-
message: WasmAgentMessage,
|
|
142
|
-
config: AgentRunConfig,
|
|
143
|
-
): Promise<TurnResult> {
|
|
144
|
-
const signal = config.signal;
|
|
145
|
-
|
|
146
|
-
const checkAbort = () => {
|
|
147
|
-
if (signal?.aborted) {
|
|
148
|
-
try {
|
|
149
|
-
unwrap(hostAbort(hostAgent.handle));
|
|
150
|
-
} catch (e) {
|
|
151
|
-
// ignore wrong_phase errors
|
|
152
|
-
}
|
|
153
|
-
throw new HostError("user_aborted", "Turn stopped by user");
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
let step = unwrap(startTurn(hostAgent.handle, { prompt: message, tools: config.llmTools }));
|
|
159
|
-
for (const event of step.events) {
|
|
160
|
-
config.onEvent?.(event);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
while (true) {
|
|
164
|
-
checkAbort();
|
|
165
|
-
const actions = step.directives ?? [];
|
|
166
|
-
if (actions.length === 0) {
|
|
167
|
-
return { aborted: false };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let stateAdvanced = false;
|
|
171
|
-
let turnFinished = false;
|
|
172
|
-
|
|
173
|
-
for (const action of actions) {
|
|
174
|
-
checkAbort();
|
|
175
|
-
switch (action.type) {
|
|
176
|
-
case "stream_llm": {
|
|
177
|
-
const stream = await config.llm.call(action.context, signal);
|
|
178
|
-
for await (const chunk of stream.chunks) {
|
|
179
|
-
checkAbort();
|
|
180
|
-
const ev = unwrap(hostFeedLlmChunk(hostAgent.handle, chunk));
|
|
181
|
-
for (const e of ev.events) config.onEvent?.(e);
|
|
182
|
-
}
|
|
183
|
-
checkAbort();
|
|
184
|
-
const result = await stream.result;
|
|
185
|
-
step = unwrap(hostLlmDone(hostAgent.handle, result));
|
|
186
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
187
|
-
stateAdvanced = true;
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
case "execute_tools": {
|
|
192
|
-
for (const call of action.calls) {
|
|
193
|
-
checkAbort();
|
|
194
|
-
const handler = config.tools[call.name];
|
|
195
|
-
let result: ToolResult;
|
|
196
|
-
if (handler) {
|
|
197
|
-
result = await handler(call);
|
|
198
|
-
} else {
|
|
199
|
-
result = {
|
|
200
|
-
content: [{ type: "text", text: `No handler for ${call.name}` }],
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
step = unwrap(hostToolDone(hostAgent.handle, call.id, result));
|
|
204
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
205
|
-
}
|
|
206
|
-
if ((step.directives ?? []).length === 0) {
|
|
207
|
-
step = unwrap(hostContinueTurn(hostAgent.handle));
|
|
208
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
209
|
-
}
|
|
210
|
-
stateAdvanced = true;
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
case "cancel_tools": {
|
|
215
|
-
for (const id of action.tool_call_ids) {
|
|
216
|
-
step = unwrap(hostToolCancelled(hostAgent.handle, id, action.reason));
|
|
217
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
218
|
-
}
|
|
219
|
-
stateAdvanced = true;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
case "summarize": {
|
|
224
|
-
if (!config.llm.summarize) {
|
|
225
|
-
throw new Error("Summarize directive received but no summarize function configured");
|
|
226
|
-
}
|
|
227
|
-
const summary = await config.llm.summarize(action.context.messages, signal);
|
|
228
|
-
step = unwrap(hostAcceptCompaction(hostAgent.handle, summary, []));
|
|
229
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
230
|
-
stateAdvanced = true;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
case "persist": {
|
|
235
|
-
const persistData = hostAgent.getPersistData();
|
|
236
|
-
await config.onPersist?.(persistData);
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case "finished":
|
|
241
|
-
turnFinished = true;
|
|
242
|
-
break;
|
|
243
|
-
|
|
244
|
-
case "wait_for_input":
|
|
245
|
-
return { aborted: false };
|
|
246
|
-
|
|
247
|
-
default:
|
|
248
|
-
console.warn(`Unknown directive type: ${(action as { type: string }).type}`);
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// If the turn is finished, return after processing all directives in this batch
|
|
254
|
-
if (turnFinished) {
|
|
255
|
-
return { aborted: false };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// If no state was advanced and there are no new directives, continue the turn
|
|
259
|
-
if (!stateAdvanced && (step.directives ?? []).length === 0) {
|
|
260
|
-
step = unwrap(hostContinueTurn(hostAgent.handle));
|
|
261
|
-
for (const e of step.events) config.onEvent?.(e);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
} catch (e: unknown) {
|
|
265
|
-
const isUserAbort =
|
|
266
|
-
(e instanceof HostError && e.code === "user_aborted") ||
|
|
267
|
-
(e instanceof DOMException && e.name === "AbortError");
|
|
268
|
-
if (isUserAbort) {
|
|
269
|
-
return { aborted: true };
|
|
270
|
-
}
|
|
271
|
-
throw e;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// ---------------------------------------------------------------------------
|
|
276
|
-
// SDK engine facade
|
|
277
|
-
// ---------------------------------------------------------------------------
|
|
278
|
-
|
|
279
|
-
export async function createEngineAgent(
|
|
280
|
-
config: AgentConfig,
|
|
281
|
-
callbacks: {
|
|
282
|
-
onEvent: (event: { type: string; payload: unknown }) => void;
|
|
283
|
-
onStatus: (status: AgentStatus) => void;
|
|
284
|
-
},
|
|
285
|
-
): Promise<HostAgent> {
|
|
286
|
-
// Load snapshot from store if available
|
|
287
|
-
let sessionState: PersistData | undefined;
|
|
288
|
-
if (config.store) {
|
|
289
|
-
const snapshot = await config.store.loadSession(config.sessionId);
|
|
290
|
-
if (snapshot) {
|
|
291
|
-
const serializer = new SnapshotSerializer();
|
|
292
|
-
sessionState = serializer.deserialize(snapshot) as PersistData | undefined;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const hostAgent = await createHostAgentInstance(config, sessionState);
|
|
297
|
-
return hostAgent;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function destroyEngineAgent(hostAgent: HostAgent): void {
|
|
301
|
-
hostAgent.destroy();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export async function runAgentTurn(
|
|
305
|
-
hostAgent: HostAgent,
|
|
306
|
-
config: AgentConfig,
|
|
307
|
-
input: string | AgentInput,
|
|
308
|
-
options: AgentRunOptions | undefined,
|
|
309
|
-
signal: AbortSignal,
|
|
310
|
-
callbacks: {
|
|
311
|
-
onEvent: (event: { type: string; payload: unknown }) => void;
|
|
312
|
-
onStatus: (status: AgentStatus) => void;
|
|
313
|
-
},
|
|
314
|
-
): Promise<AgentRunResult> {
|
|
315
|
-
// Build user message with attachments if present
|
|
316
|
-
const userMessage = buildUserMessage(input);
|
|
317
|
-
|
|
318
|
-
const eventMapper = new EventMapper();
|
|
319
|
-
const runState = eventMapper.createRunState();
|
|
320
|
-
|
|
321
|
-
// Build tool registry — supports AgentTools | AgentTools[]
|
|
322
|
-
const toolRegistry = new ToolRegistryBuilder();
|
|
323
|
-
const allTools = normalizeTools(config.tools);
|
|
324
|
-
const artifactStore = buildArtifactStore(config);
|
|
325
|
-
const toolMap = toolRegistry.build(allTools, artifactStore, config.sessionId);
|
|
326
|
-
const llmTools = toolRegistry.getLlmTools(allTools);
|
|
327
|
-
|
|
328
|
-
// Build LLM provider adapter that returns LlmStream with chunks
|
|
329
|
-
const llmProvider: AgentRunConfig["llm"] = {
|
|
330
|
-
call: async (context: LlmContext, s?: AbortSignal): Promise<LlmStream> => {
|
|
331
|
-
const effectiveSignal = s || signal;
|
|
332
|
-
callbacks.onStatus({ state: "calling_model", message: "Calling model..." });
|
|
333
|
-
|
|
334
|
-
const modelRequest = {
|
|
335
|
-
instructions: context.system_prompt,
|
|
336
|
-
messages: convertWasmMessagesToAgentMessages(context.messages),
|
|
337
|
-
tools: llmTools.map((t) => ({
|
|
338
|
-
name: t.name,
|
|
339
|
-
description: t.description,
|
|
340
|
-
inputSchema: t.parameters,
|
|
341
|
-
run: () => null, // Dummy run — LLM tools only need schema, not handler
|
|
342
|
-
})),
|
|
343
|
-
signal: effectiveSignal,
|
|
344
|
-
metadata: mergeMetadata(input, options?.metadata),
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
const response = await config.model.generate(modelRequest);
|
|
348
|
-
|
|
349
|
-
// Accumulate usage from model response into run state
|
|
350
|
-
if (response.usage) {
|
|
351
|
-
runState.usage = response.usage;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return modelResponseToLlmStream(response, effectiveSignal);
|
|
355
|
-
},
|
|
356
|
-
summarize: config.model.summarize
|
|
357
|
-
? async (wasmMessages: WasmAgentMessage[], sig?: AbortSignal) => {
|
|
358
|
-
const sdkMessages = convertWasmMessagesToAgentMessages(wasmMessages);
|
|
359
|
-
return config.model.summarize!(sdkMessages, sig);
|
|
360
|
-
}
|
|
361
|
-
: undefined,
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
const result = await runTurnWithHostAgent(hostAgent, userMessage, {
|
|
366
|
-
llm: llmProvider,
|
|
367
|
-
tools: toolMap,
|
|
368
|
-
llmTools,
|
|
369
|
-
onEvent: (rawEvent: RawAgentEvent) => {
|
|
370
|
-
const semanticEvents = eventMapper.map(rawEvent, runState);
|
|
371
|
-
for (const ev of semanticEvents) {
|
|
372
|
-
callbacks.onEvent(ev);
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
onPersist: async (data: PersistData) => {
|
|
376
|
-
callbacks.onStatus({ state: "saving", message: "Saving session..." });
|
|
377
|
-
if (config.store) {
|
|
378
|
-
const serializer = new SnapshotSerializer();
|
|
379
|
-
const snapshot = serializer.serialize(data);
|
|
380
|
-
await config.store.saveSession(config.sessionId, snapshot);
|
|
381
|
-
}
|
|
382
|
-
callbacks.onStatus({ state: "completed" });
|
|
383
|
-
},
|
|
384
|
-
artifactStore,
|
|
385
|
-
signal,
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
if (result.aborted) {
|
|
389
|
-
callbacks.onStatus({ state: "aborted", message: "Stopped by user" });
|
|
390
|
-
}
|
|
391
|
-
return eventMapper.buildRunResult(runState, result);
|
|
392
|
-
} catch (e) {
|
|
393
|
-
// Let Agent.run() handle error conversion
|
|
394
|
-
throw e;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export async function steerAgent(hostAgent: HostAgent, input: string | AgentInput): Promise<void> {
|
|
399
|
-
const text = typeof input === "string" ? input : input.text;
|
|
400
|
-
const message: WasmAgentMessage = {
|
|
401
|
-
role: "user",
|
|
402
|
-
content: [{ type: "text", text }],
|
|
403
|
-
timestamp: Date.now(),
|
|
404
|
-
};
|
|
405
|
-
hostAgent.steer(message);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export async function resetAgentState(hostAgent: HostAgent): Promise<void> {
|
|
409
|
-
unwrap(hostReset(hostAgent.handle));
|
|
410
|
-
// Caller must set engineAgent = null after this
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// --- Helpers ---
|
|
414
|
-
|
|
415
|
-
function unwrap<T>(result: { ok: boolean; data?: T; error?: { code: string; message: string } }): T {
|
|
416
|
-
if (!result.ok) {
|
|
417
|
-
throw new HostError(result.error!.code, result.error!.message);
|
|
418
|
-
}
|
|
419
|
-
return result.data!;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function normalizeTools(tools: import("../types.ts").AgentTools | import("../types.ts").AgentTools[] | undefined): import("../types.ts").AgentTools[] {
|
|
423
|
-
if (!tools) return [];
|
|
424
|
-
if (Array.isArray(tools)) return tools;
|
|
425
|
-
return [tools];
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function buildContextBudget(context?: import("../types.ts").AgentContextPolicy): import("../../pi_host_web.js").ContextProjectionBudget {
|
|
429
|
-
return {
|
|
430
|
-
max_tool_result_chars: context?.toolResultLimit ?? 50000,
|
|
431
|
-
max_context_tokens: context?.maxTokens ?? 100000,
|
|
432
|
-
microcompact_after_turns: 5,
|
|
433
|
-
compaction_threshold: 0.75,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function buildModelOptions(model: import("../types.ts").AgentModel): import("../../pi_host_web.js").Model {
|
|
438
|
-
return {
|
|
439
|
-
id: model.id ?? "custom-model",
|
|
440
|
-
name: model.id ?? "custom-model",
|
|
441
|
-
api: "anthropic",
|
|
442
|
-
provider: "anthropic",
|
|
443
|
-
reasoning: false,
|
|
444
|
-
context_window: model.contextWindow ?? 100000,
|
|
445
|
-
max_tokens: model.maxTokens ?? 4096,
|
|
446
|
-
capabilities: {
|
|
447
|
-
vision: model.capabilities?.vision ?? false,
|
|
448
|
-
json_mode: model.capabilities?.jsonMode ?? true,
|
|
449
|
-
function_calling: model.capabilities?.functionCalling ?? true,
|
|
450
|
-
streaming: model.capabilities?.streaming ?? true,
|
|
451
|
-
},
|
|
452
|
-
cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function buildArtifactStore(config: AgentConfig): ArtifactStore | undefined {
|
|
457
|
-
if (config.artifacts?.mode === "external" && config.store) {
|
|
458
|
-
const store = config.store;
|
|
459
|
-
if (typeof store.saveArtifact !== "function" || typeof store.loadArtifact !== "function") {
|
|
460
|
-
throw createAgentError(
|
|
461
|
-
"store_artifact_unsupported",
|
|
462
|
-
"Store does not support artifact operations but external artifact mode is configured",
|
|
463
|
-
{ recoverable: false },
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
return {
|
|
467
|
-
save: (sessionId: string, artifactId: string, content: string) =>
|
|
468
|
-
store.saveArtifact!(sessionId, {
|
|
469
|
-
id: artifactId,
|
|
470
|
-
kind: "text",
|
|
471
|
-
content,
|
|
472
|
-
createdAt: Date.now(),
|
|
473
|
-
}),
|
|
474
|
-
load: (sessionId: string, artifactId: string) =>
|
|
475
|
-
store.loadArtifact!(sessionId, artifactId).then((a: import("../types.ts").AgentArtifact | null) =>
|
|
476
|
-
a && typeof a.content === "string" ? a.content : null,
|
|
477
|
-
),
|
|
478
|
-
search: (sessionId: string, query: string) => {
|
|
479
|
-
if (typeof store.searchArtifacts !== "function") {
|
|
480
|
-
return Promise.resolve([]);
|
|
481
|
-
}
|
|
482
|
-
return store.searchArtifacts!(sessionId, { text: query }).then((results: import("../types.ts").ArtifactSearchResult[]) =>
|
|
483
|
-
results.map((r: import("../types.ts").ArtifactSearchResult) => ({
|
|
484
|
-
id: r.artifact.id,
|
|
485
|
-
snippet: r.snippet ?? "",
|
|
486
|
-
match_count: r.matchCount ?? 0,
|
|
487
|
-
})),
|
|
488
|
-
);
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function mergeMetadata(
|
|
496
|
-
input: string | AgentInput,
|
|
497
|
-
runMetadata?: Record<string, unknown>,
|
|
498
|
-
): Record<string, unknown> | undefined {
|
|
499
|
-
const inputMetadata = typeof input === "object" ? input.metadata : undefined;
|
|
500
|
-
if (!inputMetadata && !runMetadata) return undefined;
|
|
501
|
-
return { ...inputMetadata, ...runMetadata };
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function buildUserMessage(input: string | AgentInput): WasmAgentMessage {
|
|
505
|
-
const text = typeof input === "string" ? input : input.text;
|
|
506
|
-
const content: Content[] = [{ type: "text", text }];
|
|
507
|
-
|
|
508
|
-
if (typeof input === "object" && input.attachments) {
|
|
509
|
-
for (const attachment of input.attachments) {
|
|
510
|
-
if (attachment.type === "image" || attachment.mimeType?.startsWith("image/")) {
|
|
511
|
-
content.push({
|
|
512
|
-
type: "image",
|
|
513
|
-
media_type: attachment.mimeType ?? "image/png",
|
|
514
|
-
data: typeof attachment.content === "string" ? attachment.content : btoa(String.fromCharCode(...new Uint8Array(attachment.content))),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return {
|
|
521
|
-
role: "user",
|
|
522
|
-
content,
|
|
523
|
-
timestamp: Date.now(),
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function convertWasmMessagesToAgentMessages(
|
|
528
|
-
messages: WasmAgentMessage[],
|
|
529
|
-
): AgentMessage[] {
|
|
530
|
-
return messages.map((msg) => ({
|
|
531
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
532
|
-
role: msg.role,
|
|
533
|
-
content: msg.content.map((c) => {
|
|
534
|
-
if (c.type === "text") return { type: "text" as const, text: c.text };
|
|
535
|
-
if (c.type === "tool_call") return { type: "tool_call" as const, id: c.id, name: c.name, arguments: c.arguments };
|
|
536
|
-
if (c.type === "image") return { type: "image" as const, mimeType: c.media_type, data: c.data };
|
|
537
|
-
return { type: "text" as const, text: "" };
|
|
538
|
-
}),
|
|
539
|
-
timestamp: Date.now(),
|
|
540
|
-
tool_call_id: msg.role === "tool_result" ? (msg as unknown as { tool_call_id: string }).tool_call_id : undefined,
|
|
541
|
-
}));
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function modelResponseToLlmStream(
|
|
545
|
-
response: ModelResponse,
|
|
546
|
-
signal: AbortSignal,
|
|
547
|
-
): LlmStream {
|
|
548
|
-
const chunks: AsyncIterable<LlmChunk> = {
|
|
549
|
-
[Symbol.asyncIterator]: async function* () {
|
|
550
|
-
if (signal.aborted) return;
|
|
551
|
-
|
|
552
|
-
// Start chunk — stop_reason belongs on final result, not start
|
|
553
|
-
yield {
|
|
554
|
-
kind: "start",
|
|
555
|
-
content: [{ type: "text", text: "" }],
|
|
556
|
-
api: "sdk",
|
|
557
|
-
provider: "sdk",
|
|
558
|
-
model: response.model ?? "sdk-model",
|
|
559
|
-
stop_reason: "end_turn" as const,
|
|
560
|
-
error_message: undefined,
|
|
561
|
-
timestamp: Date.now(),
|
|
562
|
-
usage: {
|
|
563
|
-
input: response.usage?.input ?? 0,
|
|
564
|
-
output: response.usage?.output ?? 0,
|
|
565
|
-
cache_read: response.usage?.cache_read ?? 0,
|
|
566
|
-
cache_write: response.usage?.cache_write ?? 0,
|
|
567
|
-
total_tokens: response.usage?.total_tokens ?? 0,
|
|
568
|
-
},
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
// Text delta chunks for each text block
|
|
572
|
-
for (const block of response.content) {
|
|
573
|
-
if (signal.aborted) return;
|
|
574
|
-
if (block.type === "text" && block.text) {
|
|
575
|
-
// Split into words to simulate streaming
|
|
576
|
-
const words = block.text.split(/(\s+)/);
|
|
577
|
-
for (const word of words) {
|
|
578
|
-
if (signal.aborted) return;
|
|
579
|
-
if (word) {
|
|
580
|
-
yield { kind: "text_delta", text: word };
|
|
581
|
-
// Small artificial delay to simulate streaming
|
|
582
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
const result: Promise<LlmResult> = Promise.resolve({
|
|
591
|
-
Ok: {
|
|
592
|
-
content: response.content.map((c: import("../types.ts").AgentContentBlock) => {
|
|
593
|
-
if (c.type === "text") return { type: "text", text: c.text };
|
|
594
|
-
if (c.type === "tool_call") return { type: "tool_call", id: c.id, name: c.name, arguments: c.arguments };
|
|
595
|
-
return { type: "text", text: "" };
|
|
596
|
-
}),
|
|
597
|
-
api: "sdk",
|
|
598
|
-
provider: "sdk",
|
|
599
|
-
model: response.model ?? "sdk-model",
|
|
600
|
-
stop_reason: response.stopReason === "tool_call" ? "tool_use" : "end_turn",
|
|
601
|
-
error_message: undefined,
|
|
602
|
-
timestamp: Date.now(),
|
|
603
|
-
usage: {
|
|
604
|
-
input: response.usage?.input ?? 0,
|
|
605
|
-
output: response.usage?.output ?? 0,
|
|
606
|
-
cache_read: response.usage?.cache_read ?? 0,
|
|
607
|
-
cache_write: response.usage?.cache_write ?? 0,
|
|
608
|
-
total_tokens: response.usage?.total_tokens ?? 0,
|
|
609
|
-
},
|
|
610
|
-
},
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
return { chunks, result };
|
|
614
|
-
}
|