pi-agent-flow 1.0.8 → 1.2.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/runner-cli.js DELETED
@@ -1,254 +0,0 @@
1
- /**
2
- * Helpers for inheriting selected parent CLI flags in child flow processes.
3
- */
4
-
5
- import * as fs from "node:fs";
6
- import * as os from "node:os";
7
- import * as path from "node:path";
8
-
9
- function looksLikeExplicitRelativePath(value) {
10
- return (
11
- value.startsWith("./") ||
12
- value.startsWith("../") ||
13
- value.startsWith(".\\") ||
14
- value.startsWith("..\\")
15
- );
16
- }
17
-
18
- function resolvePathArg(value, options = {}) {
19
- const { allowPackageSource = false, alwaysResolveRelative = false } = options;
20
- if (!value) return value;
21
- if (allowPackageSource && (value.startsWith("npm:") || value.startsWith("git:"))) return value;
22
- if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2));
23
- if (path.isAbsolute(value)) return value;
24
-
25
- const resolved = path.resolve(process.cwd(), value);
26
- if (
27
- alwaysResolveRelative ||
28
- looksLikeExplicitRelativePath(value) ||
29
- path.extname(value) !== "" ||
30
- fs.existsSync(resolved)
31
- ) {
32
- return resolved;
33
- }
34
- return value;
35
- }
36
-
37
- /**
38
- * Parse process.argv into groups used for child flow invocations.
39
- *
40
- * - extensionArgs: forwarded with path resolution
41
- * - alwaysProxy: forwarded verbatim to every child
42
- * - fallbackModel/thinking/tools: used only when the flow file does not set them
43
- */
44
- export function parseFlowCliArgs(argv) {
45
- const extensionArgs = [];
46
- const alwaysProxy = [];
47
- let fallbackModel;
48
- let fallbackThinking;
49
- let fallbackTools;
50
- let fallbackNoTools = false;
51
- let tieredLiteModel;
52
- let tieredFlashModel;
53
- let tieredFullModel;
54
-
55
- let i = 2; // skip executable + script name
56
- while (i < argv.length) {
57
- const raw = argv[i];
58
- if (!raw.startsWith("-")) {
59
- i++;
60
- continue;
61
- }
62
-
63
- const eqIdx = raw.indexOf("=");
64
- const flagName = eqIdx !== -1 ? raw.slice(0, eqIdx) : raw;
65
- const inlineValue = eqIdx !== -1 ? raw.slice(eqIdx + 1) : undefined;
66
-
67
- const nextToken = argv[i + 1];
68
- const nextIsValue = nextToken !== undefined && !nextToken.startsWith("-");
69
-
70
- const getValue = () => {
71
- if (inlineValue !== undefined) return [inlineValue, 1];
72
- if (nextIsValue) return [nextToken, 2];
73
- return [undefined, 1];
74
- };
75
-
76
- if (
77
- [
78
- "--mode",
79
- "--session",
80
- "--append-system-prompt",
81
- "--export",
82
- "--flow-max-depth",
83
- ].includes(flagName)
84
- ) {
85
- const [, skip] = getValue();
86
- i += skip;
87
- continue;
88
- }
89
-
90
- if (["--flow-prevent-cycles", "--list-models"].includes(flagName)) {
91
- const [, skip] = getValue();
92
- i += skip;
93
- continue;
94
- }
95
-
96
- if (
97
- [
98
- "--print",
99
- "-p",
100
- "--no-session",
101
- "--continue",
102
- "-c",
103
- "--resume",
104
- "-r",
105
- "--offline",
106
- "--help",
107
- "-h",
108
- "--version",
109
- "-v",
110
- "--no-flow-prevent-cycles",
111
- ].includes(flagName)
112
- ) {
113
- i++;
114
- continue;
115
- }
116
-
117
- if (flagName === "--no-extensions" || flagName === "-ne") {
118
- extensionArgs.push(flagName);
119
- i++;
120
- continue;
121
- }
122
-
123
- if (flagName === "--extension" || flagName === "-e") {
124
- const [value, skip] = getValue();
125
- if (value !== undefined) {
126
- extensionArgs.push(flagName, resolvePathArg(value, { allowPackageSource: true }));
127
- }
128
- i += skip;
129
- continue;
130
- }
131
-
132
- if (["--skill", "--prompt-template", "--theme"].includes(flagName)) {
133
- const [value, skip] = getValue();
134
- if (value !== undefined) alwaysProxy.push(flagName, resolvePathArg(value));
135
- i += skip;
136
- continue;
137
- }
138
-
139
- if (flagName === "--session-dir") {
140
- const [value, skip] = getValue();
141
- if (value !== undefined) {
142
- alwaysProxy.push(flagName, resolvePathArg(value, { alwaysResolveRelative: true }));
143
- }
144
- i += skip;
145
- continue;
146
- }
147
-
148
- if (
149
- [
150
- "--provider",
151
- "--api-key",
152
- "--system-prompt",
153
- "--models",
154
- ].includes(flagName)
155
- ) {
156
- const [value, skip] = getValue();
157
- if (value !== undefined) alwaysProxy.push(flagName, value);
158
- i += skip;
159
- continue;
160
- }
161
-
162
- if (
163
- [
164
- "--no-skills",
165
- "-ns",
166
- "--no-prompt-templates",
167
- "-np",
168
- "--no-themes",
169
- "--verbose",
170
- ].includes(flagName)
171
- ) {
172
- alwaysProxy.push(flagName);
173
- i++;
174
- continue;
175
- }
176
-
177
- if (flagName === "--model") {
178
- const [value, skip] = getValue();
179
- if (value !== undefined) fallbackModel = value;
180
- i += skip;
181
- continue;
182
- }
183
-
184
- if (flagName === "--thinking") {
185
- const [value, skip] = getValue();
186
- if (value !== undefined) fallbackThinking = value;
187
- i += skip;
188
- continue;
189
- }
190
-
191
- if (flagName === "--tools") {
192
- const [value, skip] = getValue();
193
- if (value !== undefined) fallbackTools = value;
194
- i += skip;
195
- continue;
196
- }
197
-
198
- if (flagName === "--no-tools") {
199
- fallbackNoTools = true;
200
- i++;
201
- continue;
202
- }
203
-
204
- if (flagName === "--flow-lite-model") {
205
- const [value, skip] = getValue();
206
- if (value !== undefined) tieredLiteModel = value;
207
- i += skip;
208
- continue;
209
- }
210
-
211
- if (flagName === "--flow-flash-model") {
212
- const [value, skip] = getValue();
213
- if (value !== undefined) tieredFlashModel = value;
214
- i += skip;
215
- continue;
216
- }
217
-
218
- if (flagName === "--flow-full-model") {
219
- const [value, skip] = getValue();
220
- if (value !== undefined) tieredFullModel = value;
221
- i += skip;
222
- continue;
223
- }
224
-
225
- if (inlineValue !== undefined) {
226
- alwaysProxy.push(flagName, inlineValue);
227
- i++;
228
- continue;
229
- }
230
-
231
- if (nextIsValue) {
232
- alwaysProxy.push(flagName, nextToken);
233
- i += 2;
234
- continue;
235
- }
236
-
237
- alwaysProxy.push(flagName);
238
- i++;
239
- }
240
-
241
- return {
242
- extensionArgs,
243
- alwaysProxy,
244
- fallbackModel,
245
- fallbackThinking,
246
- fallbackTools,
247
- fallbackNoTools,
248
- tieredModels: {
249
- lite: tieredLiteModel,
250
- flash: tieredFlashModel,
251
- full: tieredFullModel,
252
- },
253
- };
254
- }
package/runner-events.js DELETED
@@ -1,405 +0,0 @@
1
- /**
2
- * Helpers for parsing Pi JSON mode events and summarizing flow results.
3
- */
4
-
5
- function getSeenFlowMessageSignatures(result) {
6
- if (!Object.prototype.hasOwnProperty.call(result, "__seenMessageSignatures")) {
7
- Object.defineProperty(result, "__seenMessageSignatures", {
8
- value: new Set(),
9
- enumerable: false,
10
- configurable: false,
11
- writable: false,
12
- });
13
- }
14
- return result.__seenMessageSignatures;
15
- }
16
-
17
- function getStreamingTextBuffer(result) {
18
- if (!Object.prototype.hasOwnProperty.call(result, "__streamingTextBuffer")) {
19
- Object.defineProperty(result, "__streamingTextBuffer", {
20
- value: "",
21
- enumerable: false,
22
- configurable: false,
23
- writable: true,
24
- });
25
- Object.defineProperty(result, "__lastEmittedWordCount", {
26
- value: 0,
27
- enumerable: false,
28
- configurable: false,
29
- writable: true,
30
- });
31
- }
32
- return result.__streamingTextBuffer;
33
- }
34
-
35
- /**
36
- * Drain the accumulated streaming text buffer and return it.
37
- * Updates the last-emitted word count for threshold tracking.
38
- */
39
- export function drainStreamingText(result) {
40
- const buf = getStreamingTextBuffer(result);
41
- if (!buf) return "";
42
- result.__streamingTextBuffer = "";
43
- result.__lastEmittedWordCount = 0;
44
- return buf;
45
- }
46
-
47
- // ---------------------------------------------------------------------------
48
- // Streaming token estimate
49
- // ---------------------------------------------------------------------------
50
-
51
- /** Chars per token heuristic for output estimation. */
52
- const CHARS_PER_TOKEN = 4;
53
-
54
- /** EMA smoothing factor for tokens-per-second (higher = more responsive). */
55
- const EMA_ALPHA = 0.15;
56
-
57
- function getStreamingEstimate(result) {
58
- if (!Object.prototype.hasOwnProperty.call(result, "__streamingEstimate")) {
59
- Object.defineProperty(result, "__streamingEstimate", {
60
- value: { chars: 0 },
61
- enumerable: false,
62
- configurable: false,
63
- writable: true,
64
- });
65
- }
66
- return result.__streamingEstimate;
67
- }
68
-
69
- /**
70
- * Lazily initialize TPS tracking properties on the result object.
71
- * - __lastEmitTime: timestamp (ms) of the last streaming emit
72
- * - __smoothedTps: EMA-smoothed tokens-per-second value
73
- */
74
- function getTpsTracker(result) {
75
- if (!Object.prototype.hasOwnProperty.call(result, "__smoothedTps")) {
76
- Object.defineProperty(result, "__smoothedTps", {
77
- value: 0,
78
- enumerable: false,
79
- configurable: false,
80
- writable: true,
81
- });
82
- Object.defineProperty(result, "__lastEmitTime", {
83
- value: 0,
84
- enumerable: false,
85
- configurable: false,
86
- writable: true,
87
- });
88
- }
89
- return result;
90
- }
91
-
92
- /**
93
- * Update the EMA-smoothed tokens-per-second based on a new sample.
94
- * Called from emitUpdate() with the estimated output tokens since last emit.
95
- * Skips the update when delta time or tokens are zero (e.g., first emit).
96
- */
97
- export function updateSmoothedTps(result, estimatedTokens) {
98
- if (estimatedTokens <= 0) return;
99
- const tracker = getTpsTracker(result);
100
- const now = Date.now();
101
- if (tracker.__lastEmitTime === 0) {
102
- // First emit — seed the value directly
103
- tracker.__lastEmitTime = now;
104
- return;
105
- }
106
- const deltaSec = (now - tracker.__lastEmitTime) / 1000;
107
- if (deltaSec <= 0) return;
108
- const instantRate = estimatedTokens / deltaSec;
109
- if (tracker.__smoothedTps === 0) {
110
- tracker.__smoothedTps = instantRate;
111
- } else {
112
- tracker.__smoothedTps = EMA_ALPHA * instantRate + (1 - EMA_ALPHA) * tracker.__smoothedTps;
113
- }
114
- tracker.__lastEmitTime = now;
115
- }
116
-
117
- /**
118
- * Return the current EMA-smoothed tokens-per-second value.
119
- */
120
- export function drainSmoothedTps(result) {
121
- const tracker = getTpsTracker(result);
122
- return tracker.__smoothedTps;
123
- }
124
-
125
- /**
126
- * Lazily initialize ctx baseline tracking properties on the result object.
127
- * - __ctxBaseline: last known real totalTokens from message_end
128
- * - __ctxStreamingChars: cumulative output chars since last baseline reset
129
- */
130
- function getCtxBaseline(result) {
131
- if (!Object.prototype.hasOwnProperty.call(result, "__ctxBaseline")) {
132
- Object.defineProperty(result, "__ctxBaseline", {
133
- value: 0,
134
- enumerable: false,
135
- configurable: false,
136
- writable: true,
137
- });
138
- Object.defineProperty(result, "__ctxStreamingChars", {
139
- value: 0,
140
- enumerable: false,
141
- configurable: false,
142
- writable: true,
143
- });
144
- }
145
- return result.__ctxBaseline;
146
- }
147
-
148
- /**
149
- * Track streaming characters and estimate output tokens.
150
- * Called on every streaming delta.
151
- */
152
- function updateStreamingEstimate(result, deltaLength) {
153
- if (deltaLength <= 0) return;
154
- const est = getStreamingEstimate(result);
155
- est.chars += deltaLength;
156
- // Also accumulate chars for ctx estimation (not drained on emit)
157
- getCtxBaseline(result);
158
- result.__ctxStreamingChars += deltaLength;
159
- }
160
-
161
- /**
162
- * Drain the current streaming estimate and return estimated output tokens.
163
- * Returns 0 when no streaming has occurred.
164
- */
165
- export function drainStreamingEstimate(result) {
166
- const est = getStreamingEstimate(result);
167
- const tokens = Math.floor(est.chars / CHARS_PER_TOKEN);
168
- est.chars = 0;
169
- return tokens;
170
- }
171
-
172
- /**
173
- * Return the estimated context tokens: last known real totalTokens (baseline)
174
- * plus any additional output tokens estimated since that baseline was set.
175
- * Returns 0 before the first message_end when no baseline exists yet,
176
- * in which case the caller should fall back to the streaming output estimate.
177
- */
178
- export function drainCtxEstimate(result) {
179
- getCtxBaseline(result);
180
- const streamingTokens = Math.floor(result.__ctxStreamingChars / CHARS_PER_TOKEN);
181
- return result.__ctxBaseline + streamingTokens;
182
- }
183
-
184
- /**
185
- * Accumulate a text or thinking delta into the streaming buffer.
186
- * Returns true if the caller should emit an update.
187
- */
188
- function accumulateStreamingDelta(result, delta) {
189
- if (!delta) return false;
190
- const buf = getStreamingTextBuffer(result);
191
- result.__streamingTextBuffer = buf + delta;
192
- updateStreamingEstimate(result, delta.length);
193
- if (result.__streamingTextBuffer.length - result.__lastEmittedWordCount >= 40) {
194
- result.__lastEmittedWordCount = result.__streamingTextBuffer.length;
195
- return true;
196
- }
197
- return false;
198
- }
199
-
200
- function stableStringify(value) {
201
- if (value === null || typeof value !== "object") {
202
- return JSON.stringify(value);
203
- }
204
-
205
- if (Array.isArray(value)) {
206
- return `[${value.map((item) => stableStringify(item)).join(",")}]`;
207
- }
208
-
209
- const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
210
- return `{${entries
211
- .map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`)
212
- .join(",")}}`;
213
- }
214
-
215
- function getMessageSignature(message) {
216
- return stableStringify(message);
217
- }
218
-
219
- function updateAssistantMetadata(result, message) {
220
- if (!message || message.role !== "assistant") return;
221
- if (!result.model && message.model) result.model = message.model;
222
- if (message.stopReason) result.stopReason = message.stopReason;
223
- if (message.errorMessage) result.errorMessage = message.errorMessage;
224
- }
225
-
226
- function addFlowAssistantMessage(result, message) {
227
- if (!message || message.role !== "assistant") return false;
228
-
229
- updateAssistantMetadata(result, message);
230
-
231
- const signature = getMessageSignature(message);
232
- const seen = getSeenFlowMessageSignatures(result);
233
- if (seen.has(signature)) return false;
234
- seen.add(signature);
235
-
236
- result.messages.push(message);
237
-
238
- // Reset streaming estimate when actual usage arrives
239
- const est = getStreamingEstimate(result);
240
- est.chars = 0;
241
-
242
- result.usage.turns++;
243
- const usage = message.usage;
244
- if (usage) {
245
- result.usage.input += usage.input || 0;
246
- result.usage.output += usage.output || 0;
247
- result.usage.cacheRead += usage.cacheRead || 0;
248
- result.usage.cacheWrite += usage.cacheWrite || 0;
249
- result.usage.cost += usage.cost?.total || 0;
250
- result.usage.contextTokens = usage.totalTokens || 0;
251
-
252
- // Snapshot ctx baseline for smooth streaming estimation
253
- result.__ctxBaseline = usage.totalTokens || 0;
254
- result.__ctxStreamingChars = 0;
255
- }
256
-
257
- // Count tool call parts in the message content
258
- if (Array.isArray(message.content)) {
259
- for (const part of message.content) {
260
- if (part.type === "toolCall") {
261
- result.usage.toolCalls++;
262
- }
263
- }
264
- }
265
-
266
- return true;
267
- }
268
-
269
- function addFlowAssistantMessages(result, messages) {
270
- if (!Array.isArray(messages)) return false;
271
- let changed = false;
272
- for (const message of messages) {
273
- if (addFlowAssistantMessage(result, message)) changed = true;
274
- }
275
- return changed;
276
- }
277
-
278
- function processFlowEvent(event, result) {
279
- if (!event || typeof event !== "object") return false;
280
-
281
- switch (event.type) {
282
- case "message_end":
283
- return addFlowAssistantMessage(result, event.message);
284
-
285
- case "turn_end":
286
- return addFlowAssistantMessage(result, event.message);
287
-
288
- case "agent_end":
289
- result.sawAgentEnd = true;
290
- return addFlowAssistantMessages(result, event.messages);
291
-
292
- case "message_update": {
293
- const evt = event.assistantMessageEvent;
294
- if (!evt || typeof evt !== "object") return false;
295
- if (evt.type === "text_delta") {
296
- return accumulateStreamingDelta(result, evt.delta);
297
- }
298
- if (evt.type === "thinking_delta") {
299
- return accumulateStreamingDelta(result, evt.delta);
300
- }
301
- return false;
302
- }
303
-
304
- default:
305
- return false;
306
- }
307
- }
308
-
309
- export function processFlowJsonLine(line, result) {
310
- if (!line.trim()) return false;
311
-
312
- let event;
313
- try {
314
- event = JSON.parse(line);
315
- } catch {
316
- return false;
317
- }
318
-
319
- return processFlowEvent(event, result);
320
- }
321
-
322
- export function getFlowFinalText(messages) {
323
- if (!Array.isArray(messages)) return "";
324
-
325
- for (let i = messages.length - 1; i >= 0; i--) {
326
- const message = messages[i];
327
- if (!message || message.role !== "assistant" || !Array.isArray(message.content)) {
328
- continue;
329
- }
330
-
331
- for (const part of message.content) {
332
- if (part?.type === "text" && typeof part.text === "string" && part.text.length > 0) {
333
- return part.text;
334
- }
335
- }
336
- }
337
-
338
- return "";
339
- }
340
-
341
- function extractNonReadToolCalls(messages) {
342
- const calls = [];
343
- if (!Array.isArray(messages)) return calls;
344
- for (const msg of messages) {
345
- if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
346
- for (const part of msg.content) {
347
- if (part.type === "toolCall" && part.name !== "read") {
348
- calls.push({ name: part.name, args: part.arguments || part.input || {} });
349
- }
350
- }
351
- }
352
- return calls;
353
- }
354
-
355
- function formatToolCallShort(tc) {
356
- const args = tc.args || {};
357
- switch (tc.name) {
358
- case "edit":
359
- case "write":
360
- return `${tc.name} ${(args.file_path || args.path || "?").split("/").pop()}`;
361
- case "bash": {
362
- const cmd = (args.command || "").replace(/[\n\r\t]+/g, " ").replace(/ +/g, " ").trim();
363
- return `bash ${cmd.length > 40 ? cmd.slice(0, 40) + "..." : cmd}`;
364
- }
365
- case "grep":
366
- return `grep /${args.pattern || "?"}/ in ${args.path || "."}`;
367
- case "find":
368
- return `find ${args.pattern || "*"} in ${args.path || "."}`;
369
- case "ls":
370
- return `ls ${args.path || "."}`;
371
- default:
372
- return tc.name;
373
- }
374
- }
375
-
376
- export function getFlowSummaryText(result) {
377
- const finalText = getFlowFinalText(result?.messages);
378
- if (finalText) return finalText;
379
-
380
- const isError =
381
- (typeof result?.exitCode === "number" && result.exitCode > 0) ||
382
- result?.stopReason === "error" ||
383
- result?.stopReason === "aborted";
384
-
385
- // Build base message for failed/aborted flows
386
- let base = "";
387
- if (typeof result?.errorMessage === "string" && result.errorMessage.trim()) {
388
- base = result.errorMessage.trim();
389
- } else if (isError && typeof result?.stderr === "string" && result.stderr.trim()) {
390
- base = result.stderr.trim();
391
- } else if (isError) {
392
- base = "Flow failed";
393
- } else {
394
- return "(no output)";
395
- }
396
-
397
- // Surface partial tool calls (excluding read) for failed/aborted flows
398
- const toolCalls = extractNonReadToolCalls(result?.messages);
399
- if (toolCalls.length > 0) {
400
- const formatted = toolCalls.map(formatToolCallShort).join(", ");
401
- return `${base}\nPartial work: ${formatted}`;
402
- }
403
-
404
- return base;
405
- }