@fragno-dev/pi-fragment 0.0.1 → 0.0.3

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.
Files changed (98) hide show
  1. package/README.md +39 -3
  2. package/dist/browser/client/react.d.ts +44 -36
  3. package/dist/browser/client/react.d.ts.map +1 -1
  4. package/dist/browser/client/react.js +105 -22
  5. package/dist/browser/client/react.js.map +1 -1
  6. package/dist/browser/client/solid.d.ts +42 -36
  7. package/dist/browser/client/solid.d.ts.map +1 -1
  8. package/dist/browser/client/solid.js +27 -13
  9. package/dist/browser/client/solid.js.map +1 -1
  10. package/dist/browser/client/svelte.d.ts +42 -36
  11. package/dist/browser/client/svelte.d.ts.map +1 -1
  12. package/dist/browser/client/svelte.js +14 -6
  13. package/dist/browser/client/svelte.js.map +1 -1
  14. package/dist/browser/client/vanilla.d.ts +99 -39
  15. package/dist/browser/client/vanilla.d.ts.map +1 -1
  16. package/dist/browser/client/vanilla.js +151 -3
  17. package/dist/browser/client/vanilla.js.map +1 -1
  18. package/dist/browser/client/vue.d.ts +54 -38
  19. package/dist/browser/client/vue.d.ts.map +1 -1
  20. package/dist/browser/client/vue.js +25 -17
  21. package/dist/browser/client/vue.js.map +1 -1
  22. package/dist/browser/{factory-DKoO_lRA.js → clients-BscY_HVe.js} +1051 -799
  23. package/dist/browser/clients-BscY_HVe.js.map +1 -0
  24. package/dist/browser/index.d.ts +3 -776
  25. package/dist/browser/index.js +801 -2
  26. package/dist/browser/index.js.map +1 -0
  27. package/dist/browser/routes-CpL_YGWK.d.ts +1560 -0
  28. package/dist/browser/routes-CpL_YGWK.d.ts.map +1 -0
  29. package/dist/cli/mod.d.ts.map +1 -1
  30. package/dist/cli/mod.js +245 -7
  31. package/dist/cli/mod.js.map +1 -1
  32. package/dist/node/{pi → client}/clients.d.ts +46 -36
  33. package/dist/node/client/clients.d.ts.map +1 -0
  34. package/dist/node/client/clients.js +54 -0
  35. package/dist/node/client/clients.js.map +1 -0
  36. package/dist/node/client/session-controller.d.ts +31 -0
  37. package/dist/node/client/session-controller.d.ts.map +1 -0
  38. package/dist/node/client/session-controller.js +33 -0
  39. package/dist/node/client/session-controller.js.map +1 -0
  40. package/dist/node/client/session-store.d.ts +71 -0
  41. package/dist/node/client/session-store.d.ts.map +1 -0
  42. package/dist/node/client/session-store.js +637 -0
  43. package/dist/node/client/session-store.js.map +1 -0
  44. package/dist/node/debug-log.d.ts +9 -0
  45. package/dist/node/debug-log.d.ts.map +1 -0
  46. package/dist/node/debug-log.js +58 -0
  47. package/dist/node/debug-log.js.map +1 -0
  48. package/dist/node/index.d.ts +5 -4
  49. package/dist/node/index.js +5 -3
  50. package/dist/node/pi/definition.d.ts +1 -1
  51. package/dist/node/pi/definition.d.ts.map +1 -1
  52. package/dist/node/pi/dsl.d.ts +5 -2
  53. package/dist/node/pi/dsl.d.ts.map +1 -1
  54. package/dist/node/pi/dsl.js +22 -3
  55. package/dist/node/pi/dsl.js.map +1 -1
  56. package/dist/node/pi/factory.d.ts +37 -34
  57. package/dist/node/pi/factory.d.ts.map +1 -1
  58. package/dist/node/pi/factory.js.map +1 -1
  59. package/dist/node/pi/mappers.js +0 -1
  60. package/dist/node/pi/mappers.js.map +1 -1
  61. package/dist/node/pi/route-schemas.js +42 -10
  62. package/dist/node/pi/route-schemas.js.map +1 -1
  63. package/dist/node/pi/types.d.ts +155 -7
  64. package/dist/node/pi/types.d.ts.map +1 -1
  65. package/dist/node/pi/types.js +6 -0
  66. package/dist/node/pi/types.js.map +1 -0
  67. package/dist/node/pi/workflow/active-session.d.ts +2 -0
  68. package/dist/node/pi/workflow/active-session.js +107 -0
  69. package/dist/node/pi/workflow/active-session.js.map +1 -0
  70. package/dist/node/pi/workflow/agent-runner.d.ts +13 -0
  71. package/dist/node/pi/workflow/agent-runner.d.ts.map +1 -0
  72. package/dist/node/pi/workflow/agent-runner.js +228 -0
  73. package/dist/node/pi/workflow/agent-runner.js.map +1 -0
  74. package/dist/node/pi/workflow/tool-journal.js +157 -0
  75. package/dist/node/pi/workflow/tool-journal.js.map +1 -0
  76. package/dist/node/pi/workflow/workflow.d.ts +29 -0
  77. package/dist/node/pi/workflow/workflow.d.ts.map +1 -0
  78. package/dist/node/pi/workflow/workflow.js +219 -0
  79. package/dist/node/pi/workflow/workflow.js.map +1 -0
  80. package/dist/node/routes.d.ts +38 -35
  81. package/dist/node/routes.d.ts.map +1 -1
  82. package/dist/node/routes.js +203 -132
  83. package/dist/node/routes.js.map +1 -1
  84. package/dist/node/schema.js +1 -1
  85. package/dist/node/schema.js.map +1 -1
  86. package/package.json +30 -29
  87. package/dist/browser/client-Bk-J98pf.d.ts +0 -679
  88. package/dist/browser/client-Bk-J98pf.d.ts.map +0 -1
  89. package/dist/browser/factory-DKoO_lRA.js.map +0 -1
  90. package/dist/browser/index.d.ts.map +0 -1
  91. package/dist/node/pi/clients.d.ts.map +0 -1
  92. package/dist/node/pi/clients.js +0 -18
  93. package/dist/node/pi/clients.js.map +0 -1
  94. package/dist/node/pi/workflow.d.ts +0 -31
  95. package/dist/node/pi/workflow.d.ts.map +0 -1
  96. package/dist/node/pi/workflow.js +0 -242
  97. package/dist/node/pi/workflow.js.map +0 -1
  98. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,3 +1,802 @@
1
- import { a as createPiWorkflows, c as SESSION_STATUSES, d as piFragmentDefinition, i as PI_WORKFLOW_NAME, l as STEERING_MODES, n as createPi, o as createPiFragmentClients, r as defineAgent, s as piRoutesFactory, t as createPiFragment, u as THINKING_LEVELS } from "./factory-DKoO_lRA.js";
1
+ import { a as STEERING_MODES, c as piFragmentDefinition, i as SESSION_STATUSES, n as createPiSessionStore, o as THINKING_LEVELS, r as piRoutesFactory, s as PiLogger, t as createPiFragmentClients } from "./clients-BscY_HVe.js";
2
+ import { z } from "zod";
3
+ import { NonRetryableError, defineWorkflow } from "@fragno-dev/workflows";
4
+ import { column, idColumn, schema } from "@fragno-dev/db/schema";
5
+ import { Agent } from "@mariozechner/pi-agent-core";
2
6
 
3
- export { PI_WORKFLOW_NAME, SESSION_STATUSES, STEERING_MODES, THINKING_LEVELS, createPi, createPiFragment, createPiFragmentClients, createPiWorkflows, defineAgent, piFragmentDefinition, piRoutesFactory };
7
+ //#region src/schema.ts
8
+ const piSchema = schema("pi-fragment", (s) => {
9
+ return s.addTable("session", (t) => {
10
+ return t.addColumn("id", idColumn()).addColumn("name", column("string").nullable()).addColumn("agent", column("string")).addColumn("status", column("string")).addColumn("steeringMode", column("string")).addColumn("metadata", column("json").nullable()).addColumn("tags", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_session_status", ["status"]).createIndex("idx_session_created", ["createdAt"]);
11
+ });
12
+ });
13
+
14
+ //#endregion
15
+ //#region src/pi/mappers.ts
16
+ const normalizeSteeringMode = (value) => {
17
+ return STEERING_MODES.includes(value) ? value : "one-at-a-time";
18
+ };
19
+ const extractAssistantText = (messages) => {
20
+ const lastAssistant = [...messages].reverse().find((message) => {
21
+ return typeof message === "object" && message !== null && "role" in message && message.role === "assistant";
22
+ });
23
+ if (!lastAssistant || typeof lastAssistant !== "object") return "";
24
+ const content = lastAssistant.content;
25
+ if (!Array.isArray(content)) return "";
26
+ return content.filter((block) => typeof block === "object" && block !== null && block.type === "text").map((block) => block.text ?? "").join("").trim();
27
+ };
28
+ const extractAssistantTextFromMessage = (message) => {
29
+ if (!message) return "";
30
+ return extractAssistantText([message]);
31
+ };
32
+
33
+ //#endregion
34
+ //#region src/pi/workflow/active-session.ts
35
+ const WAIT_FOR_USER_TIMEOUT_MS$1 = 3600 * 1e3;
36
+ const buildWaitingForUser$1 = (turn) => ({
37
+ type: "user_message",
38
+ turn,
39
+ stepKey: `waitForEvent:wait-user-${turn}`,
40
+ timeoutMs: WAIT_FOR_USER_TIMEOUT_MS$1
41
+ });
42
+ const getActiveSessionReplayBuffer = (state) => {
43
+ const replayBuffer = state.activeSessionUpdatesByTurn;
44
+ return Array.isArray(replayBuffer) ? structuredClone(replayBuffer) : [];
45
+ };
46
+ const createPiActiveSessionState = (options) => {
47
+ const listeners = /* @__PURE__ */ new Set();
48
+ const updatesByTurn = /* @__PURE__ */ new Map();
49
+ const trimPreviousTurns = (turn) => {
50
+ for (const previousTurn of updatesByTurn.keys()) if (previousTurn < turn - 1) updatesByTurn.delete(previousTurn);
51
+ };
52
+ const appendUpdate = (turn, update) => {
53
+ const existing = updatesByTurn.get(turn) ?? [];
54
+ updatesByTurn.set(turn, [...existing, structuredClone(update)]);
55
+ trimPreviousTurns(turn);
56
+ };
57
+ const exportReplayBuffer = () => structuredClone([...updatesByTurn.entries()].sort(([leftTurn], [rightTurn]) => leftTurn - rightTurn).map(([turn, updates]) => ({
58
+ turn,
59
+ updates
60
+ })));
61
+ const syncReplayBuffer = () => {
62
+ options?.onReplayBufferChange?.(exportReplayBuffer());
63
+ };
64
+ const importReplayBuffer = (buffer) => {
65
+ updatesByTurn.clear();
66
+ for (const entry of structuredClone(buffer)) for (const update of entry.updates) appendUpdate(entry.turn, update);
67
+ syncReplayBuffer();
68
+ };
69
+ if (options?.replayBuffer) importReplayBuffer(options.replayBuffer);
70
+ const notify = (update) => {
71
+ for (const listener of Array.from(listeners)) try {
72
+ listener(update);
73
+ } catch (error) {
74
+ console.warn("Pi active-session listener failed.", {
75
+ error,
76
+ updateType: update.type
77
+ });
78
+ }
79
+ };
80
+ return {
81
+ subscribe(listener) {
82
+ listeners.add(listener);
83
+ return () => {
84
+ listeners.delete(listener);
85
+ };
86
+ },
87
+ publishEvent(turn, event) {
88
+ const update = {
89
+ type: "event",
90
+ turn,
91
+ event
92
+ };
93
+ appendUpdate(turn, update);
94
+ syncReplayBuffer();
95
+ notify(update);
96
+ },
97
+ settleTurn(turn, status) {
98
+ const update = {
99
+ type: "settled",
100
+ turn,
101
+ status
102
+ };
103
+ appendUpdate(turn, update);
104
+ syncReplayBuffer();
105
+ notify(update);
106
+ },
107
+ replayTurn(turn) {
108
+ return structuredClone(updatesByTurn.get(turn) ?? []);
109
+ },
110
+ exportReplayBuffer,
111
+ importReplayBuffer,
112
+ listenerCount: () => listeners.size
113
+ };
114
+ };
115
+ const createInitialPiAgentLoopState = (messages = []) => ({
116
+ messages,
117
+ events: [],
118
+ trace: [],
119
+ summaries: [],
120
+ turn: 0,
121
+ phase: "waiting-for-user",
122
+ waitingFor: buildWaitingForUser$1(0),
123
+ activeSessionUpdatesByTurn: []
124
+ });
125
+ const ensurePiActiveSessionState = (state) => {
126
+ if (state.activeSession) return state.activeSession;
127
+ const activeSession = createPiActiveSessionState({
128
+ replayBuffer: getActiveSessionReplayBuffer(state),
129
+ onReplayBufferChange: (replayBuffer) => {
130
+ state.activeSessionUpdatesByTurn = replayBuffer;
131
+ }
132
+ });
133
+ state.activeSessionUpdatesByTurn = activeSession.exportReplayBuffer();
134
+ state.activeSession = activeSession;
135
+ return activeSession;
136
+ };
137
+
138
+ //#endregion
139
+ //#region src/pi/types.ts
140
+ const PI_TOOL_JOURNAL_VERSION = 1;
141
+
142
+ //#endregion
143
+ //#region src/pi/workflow/tool-journal.ts
144
+ const isRecord = (value) => typeof value === "object" && value !== null;
145
+ const TOOL_JOURNAL_FIELD = "toolJournal";
146
+ const persistedToolResultContentSchema = z.union([z.object({
147
+ type: z.literal("text"),
148
+ text: z.string(),
149
+ textSignature: z.string().optional()
150
+ }), z.object({
151
+ type: z.literal("image"),
152
+ data: z.string(),
153
+ mimeType: z.string()
154
+ })]);
155
+ const persistedToolResultSchema = z.object({
156
+ content: z.array(persistedToolResultContentSchema),
157
+ details: z.unknown()
158
+ });
159
+ const persistedToolCallSchema = z.object({
160
+ version: z.literal(PI_TOOL_JOURNAL_VERSION),
161
+ key: z.string(),
162
+ sessionId: z.string(),
163
+ turnId: z.string(),
164
+ toolCallId: z.string(),
165
+ toolName: z.string(),
166
+ args: z.record(z.string(), z.unknown()),
167
+ result: persistedToolResultSchema,
168
+ isError: z.boolean(),
169
+ source: z.enum(["executed", "replay"]),
170
+ capturedAt: z.number().finite(),
171
+ seq: z.number().int().nonnegative()
172
+ });
173
+ const formatToolJournalIssueLocation = (location, issue) => {
174
+ if (issue.path.length === 0) return location;
175
+ return `${location}.${issue.path.join(".")}`;
176
+ };
177
+ const buildStableToolCallKey = (sessionId, turnId, toolCallId) => `${sessionId}:${turnId}:${toolCallId}`;
178
+ const compareToolJournalEntries = (a, b) => a.seq !== b.seq ? a.seq - b.seq : a.capturedAt - b.capturedAt;
179
+ const clonePersistedToolCall = (entry) => structuredClone(entry);
180
+ const buildToolErrorResult = (error) => ({
181
+ content: [{
182
+ type: "text",
183
+ text: error instanceof Error ? error.message : String(error)
184
+ }],
185
+ details: {}
186
+ });
187
+ const extractToolErrorMessage = (result) => {
188
+ for (const block of result.content) if (block.type === "text") return block.text;
189
+ return "Tool execution failed";
190
+ };
191
+ const createPersistedToolCall = (options) => ({
192
+ version: PI_TOOL_JOURNAL_VERSION,
193
+ key: buildStableToolCallKey(options.sessionId, options.turnId, options.toolCallId),
194
+ sessionId: options.sessionId,
195
+ turnId: options.turnId,
196
+ toolCallId: options.toolCallId,
197
+ toolName: options.toolName,
198
+ args: options.args,
199
+ result: options.result,
200
+ isError: options.isError,
201
+ source: options.source,
202
+ capturedAt: Date.now(),
203
+ seq: options.seq
204
+ });
205
+ const parsePersistedToolCall = (value, location) => {
206
+ if (!isRecord(value) || Array.isArray(value)) throw new NonRetryableError(`Invalid tool journal entry at ${location}.`);
207
+ if (value["version"] !== PI_TOOL_JOURNAL_VERSION) throw new NonRetryableError(`Unsupported tool journal version at ${location}: ${String(value["version"])}`);
208
+ const parsed = persistedToolCallSchema.safeParse(value);
209
+ if (!parsed.success) {
210
+ const issue = parsed.error.issues[0];
211
+ throw new NonRetryableError(`Invalid tool journal entry at ${formatToolJournalIssueLocation(location, issue)}: ${issue.message}`);
212
+ }
213
+ return parsed.data;
214
+ };
215
+ const parsePersistedToolJournal = (assistantResult, stepName) => {
216
+ if (!isRecord(assistantResult) || Array.isArray(assistantResult)) throw new NonRetryableError(`Assistant step ${stepName} returned an invalid result object.`);
217
+ if (!(TOOL_JOURNAL_FIELD in assistantResult)) return [];
218
+ const raw = assistantResult[TOOL_JOURNAL_FIELD];
219
+ if (!Array.isArray(raw)) throw new NonRetryableError(`Assistant step ${stepName} contains an invalid tool journal.`);
220
+ return raw.map((entry, index) => parsePersistedToolCall(entry, `${stepName}[${index}]`));
221
+ };
222
+ const hydrateReplayCache = (cache, entries) => {
223
+ const sorted = [...entries].sort(compareToolJournalEntries);
224
+ for (const entry of sorted) {
225
+ if (entry.source === "replay" && cache.has(entry.key)) continue;
226
+ cache.set(entry.key, clonePersistedToolCall(entry));
227
+ }
228
+ };
229
+ const reduceBashSideEffects = (state, entry) => {
230
+ const details = isRecord(entry.result.details) ? entry.result.details : {};
231
+ const base = isRecord(state) ? state : {};
232
+ const next = {
233
+ cwd: typeof details.cwd === "string" ? details.cwd : typeof base.cwd === "string" ? base.cwd : "/",
234
+ files: isRecord(base.files) && !Array.isArray(base.files) ? { ...base.files } : {}
235
+ };
236
+ const detailsFiles = isRecord(details.files) && !Array.isArray(details.files) ? details.files : null;
237
+ if (detailsFiles) for (const [path, value] of Object.entries(detailsFiles)) next.files[path] = value;
238
+ if (Array.isArray(details.writes)) for (const item of details.writes) {
239
+ if (!isRecord(item)) continue;
240
+ const writeEntry = item;
241
+ const path = typeof writeEntry.path === "string" ? writeEntry.path : null;
242
+ if (!path) continue;
243
+ next.files[path] = typeof writeEntry.content === "string" ? writeEntry.content : "";
244
+ }
245
+ const deletes = Array.isArray(details.deletes) ? details.deletes : Array.isArray(details.deletedPaths) ? details.deletedPaths : [];
246
+ for (const path of deletes) if (typeof path === "string") delete next.files[path];
247
+ return next;
248
+ };
249
+ const buildSideEffectState = (options) => {
250
+ const reducers = {
251
+ bash: reduceBashSideEffects,
252
+ ...options.reducers
253
+ };
254
+ const state = {};
255
+ const journal = [...options.cache.values()].sort(compareToolJournalEntries);
256
+ for (const entry of journal) {
257
+ const reducer = reducers[entry.toolName];
258
+ if (!reducer) continue;
259
+ try {
260
+ state[entry.toolName] = reducer(state[entry.toolName], entry, {
261
+ key: entry.key,
262
+ sessionId: entry.sessionId,
263
+ turnId: entry.turnId
264
+ });
265
+ } catch (error) {
266
+ throw new NonRetryableError(`Tool side-effect reducer failed for ${entry.toolName}: ${error instanceof Error ? error.message : String(error)}`);
267
+ }
268
+ }
269
+ return state;
270
+ };
271
+ const replaySequenceByContext = /* @__PURE__ */ new WeakMap();
272
+ const takeNextReplaySequence = (replayContext) => {
273
+ const current = replaySequenceByContext.get(replayContext) ?? 0;
274
+ replaySequenceByContext.set(replayContext, current + 1);
275
+ return current;
276
+ };
277
+ const createReplayContext = (options) => {
278
+ const localCache = /* @__PURE__ */ new Map();
279
+ for (const [key, entry] of options.cache.entries()) localCache.set(key, clonePersistedToolCall(entry));
280
+ const replayContext = {
281
+ cache: localCache,
282
+ journal: [],
283
+ sideEffects: buildSideEffectState({
284
+ cache: localCache,
285
+ reducers: options.reducers
286
+ })
287
+ };
288
+ const maxSeq = [...localCache.values()].reduce((max, entry) => entry.seq > max ? entry.seq : max, -1);
289
+ replaySequenceByContext.set(replayContext, maxSeq + 1);
290
+ return replayContext;
291
+ };
292
+
293
+ //#endregion
294
+ //#region src/pi/workflow/agent-runner.ts
295
+ const isAssistantLikeMessage = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && value.role === "assistant" && Array.isArray(value.content) && typeof value.stopReason === "string";
296
+ const buildStreamErrorAssistantMessage = (model, error) => ({
297
+ role: "assistant",
298
+ content: [{
299
+ type: "text",
300
+ text: ""
301
+ }],
302
+ api: model.api,
303
+ provider: model.provider,
304
+ model: model.id,
305
+ usage: {
306
+ input: 0,
307
+ output: 0,
308
+ cacheRead: 0,
309
+ cacheWrite: 0,
310
+ totalTokens: 0,
311
+ cost: {
312
+ input: 0,
313
+ output: 0,
314
+ cacheRead: 0,
315
+ cacheWrite: 0,
316
+ total: 0
317
+ }
318
+ },
319
+ stopReason: "error",
320
+ errorMessage: error instanceof Error ? error.message : String(error),
321
+ timestamp: Date.now()
322
+ });
323
+ const wrapStreamFn = (streamFn) => streamFn ? async (...args) => {
324
+ const [model] = args;
325
+ const stream = await streamFn(...args);
326
+ if (typeof stream !== "object" || stream === null || Array.isArray(stream)) return stream;
327
+ const response = stream;
328
+ if (typeof response.result !== "function") return stream;
329
+ const originalResult = response.result.bind(stream);
330
+ let streamResultError;
331
+ response.result = async () => {
332
+ if (streamResultError) throw streamResultError;
333
+ try {
334
+ const result = await originalResult();
335
+ if (isAssistantLikeMessage(result)) return result;
336
+ streamResultError = /* @__PURE__ */ new Error("Stream result is not a valid assistant message.");
337
+ } catch (error) {
338
+ streamResultError = error;
339
+ }
340
+ const errorMessage = buildStreamErrorAssistantMessage(model, streamResultError);
341
+ if ("push" in stream && typeof stream.push === "function") stream.push({
342
+ type: "error",
343
+ reason: "error",
344
+ error: errorMessage
345
+ });
346
+ return errorMessage;
347
+ };
348
+ return stream;
349
+ } : void 0;
350
+ const resolveTool = async (name, factory, context) => {
351
+ if (!factory) throw new NonRetryableError(`Tool ${name} not found.`);
352
+ if (typeof factory === "function") {
353
+ const tool = await factory(context);
354
+ if (!tool) throw new NonRetryableError(`Tool ${name} returned no definition.`);
355
+ return tool;
356
+ }
357
+ return factory;
358
+ };
359
+ const wrapToolWithReplay = (options) => ({
360
+ ...options.tool,
361
+ execute: async (toolCallId, params, signal, onUpdate) => {
362
+ const sessionId = options.context.session.id;
363
+ const toolCallIdValue = String(toolCallId);
364
+ const key = buildStableToolCallKey(sessionId, options.context.turnId, toolCallIdValue);
365
+ const replayEntry = options.context.replay.cache.get(key);
366
+ if (replayEntry) {
367
+ options.context.replay.journal.push(clonePersistedToolCall({
368
+ ...replayEntry,
369
+ source: "replay",
370
+ seq: takeNextReplaySequence(options.context.replay)
371
+ }));
372
+ PiLogger.debug("tool replay hit", {
373
+ sessionId,
374
+ turnId: options.context.turnId,
375
+ toolName: replayEntry.toolName,
376
+ key
377
+ });
378
+ if (replayEntry.isError) throw new Error(extractToolErrorMessage(replayEntry.result));
379
+ return structuredClone(replayEntry.result);
380
+ }
381
+ const argsSnapshot = structuredClone(params);
382
+ const recordResult = (result, isError) => {
383
+ const entry = createPersistedToolCall({
384
+ sessionId,
385
+ turnId: options.context.turnId,
386
+ toolCallId: toolCallIdValue,
387
+ toolName: options.toolName,
388
+ args: argsSnapshot,
389
+ result,
390
+ isError,
391
+ source: "executed",
392
+ seq: takeNextReplaySequence(options.context.replay)
393
+ });
394
+ options.context.replay.cache.set(entry.key, clonePersistedToolCall(entry));
395
+ options.context.replay.journal.push(clonePersistedToolCall(entry));
396
+ return entry;
397
+ };
398
+ try {
399
+ const result = await options.tool.execute(toolCallId, params, signal, onUpdate);
400
+ recordResult(structuredClone(result), false);
401
+ return result;
402
+ } catch (error) {
403
+ recordResult(buildToolErrorResult(error), true);
404
+ throw error;
405
+ }
406
+ }
407
+ });
408
+ const resolveTools = async (options) => {
409
+ const toolNames = options.agent.tools ?? [];
410
+ if (toolNames.length === 0) return [];
411
+ const context = {
412
+ session: options.session,
413
+ turnId: options.turnId,
414
+ toolConfig: options.agent.toolConfig ?? null,
415
+ messages: options.messages,
416
+ replay: options.replay
417
+ };
418
+ const resolved = [];
419
+ for (const name of toolNames) {
420
+ const tool = await resolveTool(name, options.tools[name], context);
421
+ resolved.push(wrapToolWithReplay({
422
+ toolName: name,
423
+ tool,
424
+ context
425
+ }));
426
+ }
427
+ return resolved;
428
+ };
429
+ const findLastAssistantMessage = (messages) => messages.findLast((m) => m.role === "assistant") ?? null;
430
+ const createAgent = async (options) => {
431
+ const now = /* @__PURE__ */ new Date();
432
+ const session = {
433
+ id: options.params.sessionId,
434
+ name: null,
435
+ status: "active",
436
+ agent: options.params.agentName,
437
+ steeringMode: options.steeringMode,
438
+ metadata: null,
439
+ tags: [],
440
+ createdAt: now,
441
+ updatedAt: now
442
+ };
443
+ const agentTools = await resolveTools({
444
+ agent: options.agent,
445
+ tools: options.tools,
446
+ session,
447
+ turnId: options.turnId,
448
+ messages: options.messages,
449
+ replay: options.replay
450
+ });
451
+ const initialState = {
452
+ systemPrompt: options.params.systemPrompt ?? options.agent.systemPrompt,
453
+ model: options.agent.model,
454
+ tools: agentTools,
455
+ messages: options.messages
456
+ };
457
+ if (options.agent.thinkingLevel) initialState.thinkingLevel = options.agent.thinkingLevel;
458
+ const agent = new Agent({
459
+ initialState,
460
+ streamFn: wrapStreamFn(options.agent.streamFn),
461
+ convertToLlm: options.agent.convertToLlm,
462
+ transformContext: options.agent.transformContext,
463
+ getApiKey: options.agent.getApiKey,
464
+ thinkingBudgets: options.agent.thinkingBudgets,
465
+ maxRetryDelayMs: options.agent.maxRetryDelayMs,
466
+ sessionId: options.params.sessionId
467
+ });
468
+ agent.setSteeringMode(options.steeringMode);
469
+ const trace = [];
470
+ const unsubscribe = agent.subscribe((event) => {
471
+ trace.push(event);
472
+ options.onEvent?.(event);
473
+ if (!options.agent.onEvent) return;
474
+ try {
475
+ options.agent.onEvent(event, {
476
+ sessionId: options.params.sessionId,
477
+ turnId: options.turnId
478
+ });
479
+ } catch (error) {
480
+ console.warn("Agent onEvent hook failed.", {
481
+ error,
482
+ sessionId: options.params.sessionId,
483
+ turnId: options.turnId,
484
+ agent: options.agent.name
485
+ });
486
+ }
487
+ });
488
+ try {
489
+ await agent.continue();
490
+ } finally {
491
+ unsubscribe();
492
+ }
493
+ const assistant = findLastAssistantMessage(agent.state.messages);
494
+ const stateError = agent.state.error;
495
+ if (stateError) throw stateError instanceof Error ? stateError : new Error(String(stateError));
496
+ if (assistant && "errorMessage" in assistant && typeof assistant.errorMessage === "string") throw new Error(assistant.errorMessage);
497
+ return {
498
+ agent,
499
+ trace,
500
+ assistant,
501
+ toolJournal: options.replay.journal.map(clonePersistedToolCall)
502
+ };
503
+ };
504
+ const runAgentTurn = async (options) => {
505
+ const result = await createAgent(options);
506
+ return {
507
+ messages: result.agent.state.messages,
508
+ trace: result.trace,
509
+ assistant: result.assistant,
510
+ toolJournal: result.toolJournal
511
+ };
512
+ };
513
+
514
+ //#endregion
515
+ //#region src/pi/workflow/workflow.ts
516
+ const PI_WORKFLOW_NAME = "agent-loop-workflow";
517
+ const WAIT_FOR_USER_TIMEOUT = "1 hour";
518
+ const WAIT_FOR_USER_TIMEOUT_MS = 3600 * 1e3;
519
+ const agentLoopParamsSchema = z.object({
520
+ sessionId: z.string(),
521
+ agentName: z.string(),
522
+ systemPrompt: z.string().optional(),
523
+ initialMessages: z.array(z.custom()).optional()
524
+ });
525
+ const userMessageSchema = z.object({
526
+ text: z.string().optional(),
527
+ done: z.boolean().optional(),
528
+ steeringMode: z.enum(["all", "one-at-a-time"]).optional()
529
+ });
530
+ const buildWaitingForUser = (turn) => ({
531
+ type: "user_message",
532
+ turn,
533
+ stepKey: `waitForEvent:wait-user-${turn}`,
534
+ timeoutMs: WAIT_FOR_USER_TIMEOUT_MS
535
+ });
536
+ const initLoopState = (params, existingState) => {
537
+ const activeSession = existingState ? ensurePiActiveSessionState(existingState) : createPiActiveSessionState();
538
+ return {
539
+ messages: Array.isArray(params.initialMessages) ? params.initialMessages : [],
540
+ events: [],
541
+ trace: [],
542
+ summaries: [],
543
+ turn: 0,
544
+ phase: "waiting-for-user",
545
+ waitingFor: buildWaitingForUser(0),
546
+ replayCache: /* @__PURE__ */ new Map(),
547
+ activeSession
548
+ };
549
+ };
550
+ const setPhase = (loop, phase) => {
551
+ loop.phase = phase;
552
+ switch (phase) {
553
+ case "waiting-for-user":
554
+ loop.waitingFor = buildWaitingForUser(loop.turn);
555
+ break;
556
+ case "running-agent":
557
+ loop.waitingFor = {
558
+ type: "assistant",
559
+ turn: loop.turn,
560
+ stepKey: `do:assistant-${loop.turn}`
561
+ };
562
+ break;
563
+ case "complete":
564
+ loop.waitingFor = null;
565
+ break;
566
+ }
567
+ };
568
+ const emitState = (ctx, loop) => {
569
+ ctx?.setState({
570
+ messages: loop.messages,
571
+ events: loop.events,
572
+ trace: loop.trace,
573
+ summaries: loop.summaries,
574
+ turn: loop.turn,
575
+ phase: loop.phase,
576
+ waitingFor: loop.waitingFor,
577
+ activeSession: loop.activeSession,
578
+ activeSessionUpdatesByTurn: loop.activeSession.exportReplayBuffer()
579
+ });
580
+ };
581
+ const mutateSessionStatus = (forSchema, sessionId, status) => {
582
+ try {
583
+ forSchema(piSchema).update("session", sessionId, (builder) => builder.set({ status }));
584
+ } catch {}
585
+ };
586
+ const projectSessionStatus = (tx, sessionId, status) => {
587
+ tx.mutate(({ forSchema }) => {
588
+ mutateSessionStatus(forSchema, sessionId, status);
589
+ });
590
+ };
591
+ const buildDetailEvent = (turn, event) => {
592
+ const timestamp = event.timestamp instanceof Date ? event.timestamp : new Date(event.timestamp);
593
+ return {
594
+ id: `${event.type}:${turn}:${timestamp.getTime()}`,
595
+ type: event.type,
596
+ payload: event.payload ?? null,
597
+ createdAt: timestamp,
598
+ deliveredAt: timestamp,
599
+ consumedByStepKey: `waitForEvent:wait-user-${turn}`
600
+ };
601
+ };
602
+ const buildTurnSummary = (turn, assistant) => {
603
+ if (!assistant) return null;
604
+ return {
605
+ turn,
606
+ assistant,
607
+ summary: extractAssistantTextFromMessage(assistant) || null
608
+ };
609
+ };
610
+ const parseAssistantStepResult = (value, stepName) => {
611
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new NonRetryableError(`Assistant step ${stepName} returned an invalid result.`);
612
+ const result = value;
613
+ if (!Array.isArray(result.messages)) throw new NonRetryableError(`Assistant step ${stepName} is missing messages.`);
614
+ const messages = result.messages;
615
+ return {
616
+ messages,
617
+ trace: Array.isArray(result.trace) ? result.trace : [],
618
+ assistant: (result.assistant && typeof result.assistant === "object" ? result.assistant : null) ?? messages.findLast((m) => m.role === "assistant") ?? null,
619
+ toolJournal: parsePersistedToolJournal(value, stepName)
620
+ };
621
+ };
622
+ const createPiAgentLoopWorkflow = (options) => defineWorkflow({
623
+ name: PI_WORKFLOW_NAME,
624
+ schema: agentLoopParamsSchema,
625
+ initialState: createInitialPiAgentLoopState()
626
+ }, async function(event, step) {
627
+ const params = agentLoopParamsSchema.parse(event.payload ?? {});
628
+ const agentDefinition = options.agents[params.agentName];
629
+ if (!agentDefinition) throw new NonRetryableError(`Agent ${params.agentName} not found.`);
630
+ const loop = initLoopState(params, this?.getState());
631
+ emitState(this, loop);
632
+ while (true) {
633
+ setPhase(loop, "waiting-for-user");
634
+ emitState(this, loop);
635
+ const userEvent = await step.waitForEvent(`wait-user-${loop.turn}`, {
636
+ type: "user_message",
637
+ timeout: WAIT_FOR_USER_TIMEOUT
638
+ });
639
+ const userPayload = userMessageSchema.parse(userEvent.payload ?? {});
640
+ const userText = userPayload.text ?? "";
641
+ const isDone = userPayload.done ?? false;
642
+ const steeringMode = normalizeSteeringMode(userPayload.steeringMode);
643
+ loop.events = [...loop.events, buildDetailEvent(loop.turn, userEvent)];
644
+ loop.messages = (await step.do(`user-${loop.turn}`, async (tx) => {
645
+ projectSessionStatus(tx, event.instanceId, "active");
646
+ const userMessage = {
647
+ role: "user",
648
+ content: [{
649
+ type: "text",
650
+ text: userText
651
+ }],
652
+ timestamp: Date.now()
653
+ };
654
+ return { messages: [...loop.messages, userMessage] };
655
+ })).messages;
656
+ setPhase(loop, "running-agent");
657
+ emitState(this, loop);
658
+ const replay = createReplayContext({
659
+ cache: loop.replayCache,
660
+ reducers: options.toolSideEffectReducers
661
+ });
662
+ const assistantStepName = `assistant-${loop.turn}`;
663
+ const traceLengthBeforeTurn = loop.trace.length;
664
+ const turnId = `${event.instanceId}:${loop.turn}`;
665
+ let assistantResult;
666
+ try {
667
+ assistantResult = await step.do(assistantStepName, { retries: {
668
+ limit: 1,
669
+ delay: "0 ms",
670
+ backoff: "constant"
671
+ } }, async (tx) => {
672
+ tx.onTerminalError.mutate(({ forSchema }) => {
673
+ mutateSessionStatus(forSchema, event.instanceId, "errored");
674
+ });
675
+ const result = await runAgentTurn({
676
+ params,
677
+ agent: agentDefinition,
678
+ tools: options.tools,
679
+ messages: loop.messages,
680
+ steeringMode,
681
+ turnId,
682
+ replay,
683
+ onEvent: (agentEvent) => {
684
+ loop.trace = [...loop.trace, agentEvent];
685
+ loop.activeSession.publishEvent(loop.turn, agentEvent);
686
+ emitState(this, loop);
687
+ }
688
+ });
689
+ projectSessionStatus(tx, event.instanceId, isDone ? "complete" : "waiting");
690
+ return result;
691
+ });
692
+ } catch (error) {
693
+ if (!(error instanceof Error && error.name === "RunnerStepSuspended")) {
694
+ loop.activeSession.settleTurn(loop.turn, "errored");
695
+ emitState(this, loop);
696
+ }
697
+ throw error;
698
+ }
699
+ const parsed = parseAssistantStepResult(assistantResult, assistantStepName);
700
+ loop.messages = parsed.messages;
701
+ loop.trace = [...loop.trace.slice(0, traceLengthBeforeTurn), ...parsed.trace];
702
+ const summary = buildTurnSummary(loop.turn, parsed.assistant);
703
+ if (summary) loop.summaries = [...loop.summaries, summary];
704
+ hydrateReplayCache(loop.replayCache, parsed.toolJournal);
705
+ if (isDone) {
706
+ setPhase(loop, "complete");
707
+ loop.activeSession.settleTurn(loop.turn, "complete");
708
+ emitState(this, loop);
709
+ return { messages: loop.messages };
710
+ }
711
+ loop.activeSession.settleTurn(loop.turn, "waiting-for-user");
712
+ loop.turn += 1;
713
+ emitState(this, loop);
714
+ }
715
+ });
716
+ const createPiWorkflows = (options) => {
717
+ PiLogger.reset();
718
+ if (options.logging) PiLogger.configure(options.logging);
719
+ return { agentLoop: createPiAgentLoopWorkflow(options) };
720
+ };
721
+
722
+ //#endregion
723
+ //#region src/pi/dsl.ts
724
+ const defineAgent = (name, definition) => {
725
+ if (definition.name && definition.name !== name) throw new Error(`defineAgent name mismatch: expected ${name}, got ${definition.name}`);
726
+ return {
727
+ ...definition,
728
+ name
729
+ };
730
+ };
731
+ const createPi = () => {
732
+ const agents = {};
733
+ const tools = {};
734
+ const toolSideEffectReducers = {};
735
+ let defaultSteeringMode;
736
+ let logging;
737
+ const builder = {
738
+ agent(definition) {
739
+ agents[definition.name] = definition;
740
+ return builder;
741
+ },
742
+ agents(registry) {
743
+ Object.assign(agents, registry);
744
+ return builder;
745
+ },
746
+ tool(name, tool) {
747
+ tools[name] = tool;
748
+ return builder;
749
+ },
750
+ tools(registry) {
751
+ Object.assign(tools, registry);
752
+ return builder;
753
+ },
754
+ toolSideEffectReducer(toolName, reducer) {
755
+ toolSideEffectReducers[toolName] = reducer;
756
+ return builder;
757
+ },
758
+ toolSideEffectReducers(registry) {
759
+ Object.assign(toolSideEffectReducers, registry);
760
+ return builder;
761
+ },
762
+ defaultSteeringMode(mode) {
763
+ defaultSteeringMode = mode;
764
+ return builder;
765
+ },
766
+ logging(config) {
767
+ logging = config;
768
+ return builder;
769
+ },
770
+ build() {
771
+ const agentsSnapshot = { ...agents };
772
+ const toolsSnapshot = { ...tools };
773
+ const reducersSnapshot = { ...toolSideEffectReducers };
774
+ return {
775
+ config: {
776
+ agents: agentsSnapshot,
777
+ tools: toolsSnapshot,
778
+ defaultSteeringMode,
779
+ toolSideEffectReducers: reducersSnapshot,
780
+ logging
781
+ },
782
+ workflows: createPiWorkflows({
783
+ agents: agentsSnapshot,
784
+ tools: toolsSnapshot,
785
+ toolSideEffectReducers: reducersSnapshot,
786
+ logging
787
+ })
788
+ };
789
+ }
790
+ };
791
+ return builder;
792
+ };
793
+
794
+ //#endregion
795
+ //#region src/pi/factory.ts
796
+ function createPiFragment(config, options, services) {
797
+ return {};
798
+ }
799
+
800
+ //#endregion
801
+ export { PI_TOOL_JOURNAL_VERSION, PI_WORKFLOW_NAME, SESSION_STATUSES, STEERING_MODES, THINKING_LEVELS, createPi, createPiFragment, createPiFragmentClients, createPiSessionStore, createPiWorkflows, defineAgent, piFragmentDefinition, piRoutesFactory };
802
+ //# sourceMappingURL=index.js.map