pi-subagents-lite 1.4.4 → 1.4.6
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/package.json +1 -1
- package/src/agents/agent-manager.ts +14 -3
- package/src/agents/agent-runner.ts +20 -3
- package/src/agents/usage.ts +7 -0
- package/src/config/config-io.ts +1 -0
- package/src/config/config-store.ts +7 -0
- package/src/config/types.ts +1 -0
- package/src/index.ts +5 -1
- package/src/shell.ts +28 -0
- package/src/types.ts +4 -2
- package/src/ui/menu/menu-widget-settings.ts +2 -0
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { runAgent } from "./agent-runner.js";
|
|
10
10
|
import { AgentOutputLog } from "./output-file.js";
|
|
11
|
+
import { getStore } from "../shell.js";
|
|
11
12
|
import {
|
|
12
13
|
type AgentRecord,
|
|
13
14
|
type AgentStatus,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type ToolActivity,
|
|
19
20
|
} from "../types.js";
|
|
20
21
|
import type { SubagentType } from "./types.js";
|
|
21
|
-
import { addUsage, getLifetimeTotal, getSessionContextPercent, type
|
|
22
|
+
import { addUsage, getLifetimeTotal, getSessionContextPercent, type AgentUsage } from "./usage.js";
|
|
22
23
|
import { errorMessage } from "../utils.js";
|
|
23
24
|
|
|
24
25
|
/** How often to check for expired agent records (milliseconds). */
|
|
@@ -373,7 +374,7 @@ export class AgentManager {
|
|
|
373
374
|
options?: Pick<SpawnOptions, "onToolActivity" | "onAssistantUsage" | "onCompaction">,
|
|
374
375
|
): {
|
|
375
376
|
onToolActivity: (activity: ToolActivity) => void;
|
|
376
|
-
onAssistantUsage: (usage:
|
|
377
|
+
onAssistantUsage: (usage: AgentUsage) => void;
|
|
377
378
|
onCompaction: (info: CompactionInfo) => void;
|
|
378
379
|
} {
|
|
379
380
|
return {
|
|
@@ -382,7 +383,17 @@ export class AgentManager {
|
|
|
382
383
|
options?.onToolActivity?.(activity);
|
|
383
384
|
},
|
|
384
385
|
onAssistantUsage: (usage) => {
|
|
385
|
-
|
|
386
|
+
// vLLM doesn't report cache hits, so usage.input is full prompt_tokens.
|
|
387
|
+
// Estimate new tokens as delta from previous message's input.
|
|
388
|
+
const deltaEnabled = getStore().agent.deltaInputTokens;
|
|
389
|
+
const cacheRead = usage.cacheRead;
|
|
390
|
+
let inputDelta = usage.input;
|
|
391
|
+
if (deltaEnabled && cacheRead === 0 && record.stats.prevInputTokens != null && usage.input > record.stats.prevInputTokens) {
|
|
392
|
+
inputDelta = usage.input - record.stats.prevInputTokens;
|
|
393
|
+
}
|
|
394
|
+
record.stats.prevInputTokens = usage.input;
|
|
395
|
+
|
|
396
|
+
addUsage(record.stats.lifetimeUsage, { ...usage, input: inputDelta });
|
|
386
397
|
options?.onAssistantUsage?.(usage);
|
|
387
398
|
},
|
|
388
399
|
onCompaction: (info) => {
|
|
@@ -20,14 +20,14 @@ import {
|
|
|
20
20
|
} from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { getAgentConfig, getConfig, getToolNamesForType, resolveVisibleTools } from "./agent-types.js";
|
|
22
22
|
import { extractText } from "../prompt/context.js";
|
|
23
|
-
import type {
|
|
23
|
+
import type { AgentUsage } from "./usage.js";
|
|
24
24
|
import { findModelInRegistry, GIT_EXEC_TIMEOUT_MS } from "../utils.js";
|
|
25
25
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
26
26
|
import { buildAgentPrompt, type PromptExtras } from "../prompt/prompts.js";
|
|
27
27
|
import { preloadSkills, loadSkillMeta, type SkillMeta } from "../prompt/skill-loader.js";
|
|
28
28
|
import { type EnvInfo, type RunCallbacks, type RunTunables, SHORT_ID_LENGTH } from "../types.js";
|
|
29
29
|
import type { SubagentType, SystemPromptMode } from "./types.js";
|
|
30
|
-
import { getStore } from "../shell.js";
|
|
30
|
+
import { getStore, enterSubagentSpawn, exitSubagentSpawn } from "../shell.js";
|
|
31
31
|
import { DEFAULT_GRACE_TURNS, CUSTOM_PROMPT_PATH } from "../config/config-io.js";
|
|
32
32
|
|
|
33
33
|
/** Normalize max turns. undefined or 0 = unlimited, otherwise minimum 1. */
|
|
@@ -106,13 +106,14 @@ function forwardAbortSignal(session: AgentSession, signal?: AbortSignal): () =>
|
|
|
106
106
|
* assistant messages at runtime, but this shape isn't reflected in the
|
|
107
107
|
* AgentSessionEvent public types.
|
|
108
108
|
*/
|
|
109
|
-
function usageFromAssistantMessage(msg: Record<string, unknown>):
|
|
109
|
+
function usageFromAssistantMessage(msg: Record<string, unknown>): AgentUsage | undefined {
|
|
110
110
|
const usage = msg.usage as Record<string, unknown> | undefined;
|
|
111
111
|
if (!usage) return undefined;
|
|
112
112
|
return {
|
|
113
113
|
input: (usage.input as number) ?? 0,
|
|
114
114
|
output: (usage.output as number) ?? 0,
|
|
115
115
|
cacheWrite: (usage.cacheWrite as number) ?? 0,
|
|
116
|
+
cacheRead: (usage.cacheRead as number) ?? 0,
|
|
116
117
|
cost: ((usage.cost as Record<string, unknown>)?.total as number) ?? 0,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
@@ -516,6 +517,22 @@ export async function runAgent(
|
|
|
516
517
|
type: SubagentType,
|
|
517
518
|
prompt: string,
|
|
518
519
|
options: RunOptions,
|
|
520
|
+
): Promise<RunResult> {
|
|
521
|
+
// Bracket the whole subagent lifecycle so the extension factory can detect
|
|
522
|
+
// it's being re-loaded inside a subagent and avoid clobbering the parent shell.
|
|
523
|
+
enterSubagentSpawn();
|
|
524
|
+
try {
|
|
525
|
+
return await runAgentImpl(ctx, type, prompt, options);
|
|
526
|
+
} finally {
|
|
527
|
+
exitSubagentSpawn();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function runAgentImpl(
|
|
532
|
+
ctx: ExtensionContext,
|
|
533
|
+
type: SubagentType,
|
|
534
|
+
prompt: string,
|
|
535
|
+
options: RunOptions,
|
|
519
536
|
): Promise<RunResult> {
|
|
520
537
|
const store = getStore();
|
|
521
538
|
const config = getConfig(type, store.agent.loadSkillsImplicitly, store.agent.loadExtensionsImplicitly);
|
package/src/agents/usage.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export type LifetimeUsage = { input: number; output: number; cacheWrite: number; cost: number };
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A single per-turn usage event as emitted upstream. Adds `cacheRead`, which
|
|
14
|
+
* LifetimeUsage omits from totals (see issue #38). Used to estimate input
|
|
15
|
+
* deltas for providers like vLLM that don't report cache hits.
|
|
16
|
+
*/
|
|
17
|
+
export type AgentUsage = LifetimeUsage & { cacheRead: number };
|
|
18
|
+
|
|
12
19
|
/** Sum of lifetime usage components (including cost), or 0 if undefined. */
|
|
13
20
|
export function getLifetimeTotal(u?: LifetimeUsage): number {
|
|
14
21
|
return u ? u.input + u.output + u.cacheWrite + u.cost : 0;
|
package/src/config/config-io.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface ResolvedAgentSettings {
|
|
|
74
74
|
readonly showContext: boolean;
|
|
75
75
|
/** Whether to show elapsed time in widget stats line. */
|
|
76
76
|
readonly showTime: boolean;
|
|
77
|
+
/** Whether to estimate input token delta for vLLM (no cache reporting). */
|
|
78
|
+
readonly deltaInputTokens: boolean;
|
|
77
79
|
/** Buffer size for streaming thinking blocks to output file. 0 = disabled. */
|
|
78
80
|
readonly outputThinkingBufferSize: number;
|
|
79
81
|
}
|
|
@@ -133,6 +135,7 @@ export class ConfigStore {
|
|
|
133
135
|
showOutput: a.showOutput !== false,
|
|
134
136
|
showContext: a.showContext !== false,
|
|
135
137
|
showTime: a.showTime !== false,
|
|
138
|
+
deltaInputTokens: a.deltaInputTokens !== false,
|
|
136
139
|
outputThinkingBufferSize: a.outputThinkingBufferSize ?? 0,
|
|
137
140
|
};
|
|
138
141
|
}
|
|
@@ -265,6 +268,10 @@ export class ConfigStore {
|
|
|
265
268
|
setShowOutput: (enabled: boolean) => this.setAgentVisibility("showOutput", enabled),
|
|
266
269
|
setShowContext: (enabled: boolean) => this.setAgentVisibility("showContext", enabled),
|
|
267
270
|
setShowTime: (enabled: boolean) => this.setAgentVisibility("showTime", enabled),
|
|
271
|
+
setDeltaInputTokens: (enabled: boolean): void => {
|
|
272
|
+
this.config.agent.deltaInputTokens = enabled;
|
|
273
|
+
this.persist();
|
|
274
|
+
},
|
|
268
275
|
setOutputThinkingBufferSize: (size: number): void => {
|
|
269
276
|
this.config.agent.outputThinkingBufferSize = size;
|
|
270
277
|
this.persist();
|
package/src/config/types.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -24,11 +24,15 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { setPiInstance } from "./shell.js";
|
|
27
|
+
import { setPiInstance, isInsideSubagentSpawn } from "./shell.js";
|
|
28
28
|
import { registerTools } from "./registration.js";
|
|
29
29
|
import { setupEventListeners } from "./events.js";
|
|
30
30
|
|
|
31
31
|
export default function (pi: ExtensionAPI) {
|
|
32
|
+
// Subagents re-load this extension under their own pi/runtime. Stay inert so
|
|
33
|
+
// we never overwrite the parent-owned shell (pi, sessionCtx, manager, ...).
|
|
34
|
+
// The completion nudge relies on those still pointing at the parent session.
|
|
35
|
+
if (isInsideSubagentSpawn()) return;
|
|
32
36
|
setPiInstance(pi);
|
|
33
37
|
registerTools(pi);
|
|
34
38
|
setupEventListeners(pi);
|
package/src/shell.ts
CHANGED
|
@@ -99,3 +99,31 @@ export function setWidget(w: AgentWidget | null): void {
|
|
|
99
99
|
export function setCoordinator(c: SpawnCoordinator | null): void {
|
|
100
100
|
shell.coordinator = c;
|
|
101
101
|
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Subagent spawn context
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Nesting depth of in-flight subagent spawns.
|
|
109
|
+
*
|
|
110
|
+
* Subagents are created via runAgent(), which re-loads this extension fresh
|
|
111
|
+
* (new runtime, new pi/ctx). Without protection those re-loads clobber the
|
|
112
|
+
* parent-owned shell singletons below, so the nudge would later route to a
|
|
113
|
+
* dead subagent session instead of the parent. The factory checks this flag
|
|
114
|
+
* and stays inert while a subagent is spawning.
|
|
115
|
+
*/
|
|
116
|
+
let subagentSpawnDepth = 0;
|
|
117
|
+
|
|
118
|
+
export function enterSubagentSpawn(): void {
|
|
119
|
+
subagentSpawnDepth++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function exitSubagentSpawn(): void {
|
|
123
|
+
if (subagentSpawnDepth > 0) subagentSpawnDepth--;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** True while a subagent is being spawned (factory/session_start run in subagent context). */
|
|
127
|
+
export function isInsideSubagentSpawn(): boolean {
|
|
128
|
+
return subagentSpawnDepth > 0;
|
|
129
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { Model } from "@earendil-works/pi-ai";
|
|
6
6
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import type { AgentOutputLog } from "./agents/output-file.js";
|
|
8
|
-
import type { LifetimeUsage } from "./agents/usage.js";
|
|
8
|
+
import type { LifetimeUsage, AgentUsage } from "./agents/usage.js";
|
|
9
9
|
import type { SubagentType, AgentInvocation } from "./agents/types.js";
|
|
10
10
|
|
|
11
11
|
/** Thinking level for agent models. */
|
|
@@ -59,7 +59,7 @@ export interface RunCallbacks {
|
|
|
59
59
|
onTextDelta?: (delta: string, fullText: string) => void;
|
|
60
60
|
onSessionCreated?: (session: AgentSession) => void;
|
|
61
61
|
onTurnEnd?: (turnCount: number) => void;
|
|
62
|
-
onAssistantUsage?: (usage:
|
|
62
|
+
onAssistantUsage?: (usage: AgentUsage) => void;
|
|
63
63
|
onCompaction?: (info: CompactionInfo) => void;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -156,6 +156,8 @@ export interface AgentAccumulatedStats {
|
|
|
156
156
|
maxTurns?: number;
|
|
157
157
|
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
158
158
|
compactionCount: number;
|
|
159
|
+
/** Previous input token count for delta estimation (vLLM doesn't report cache hits). */
|
|
160
|
+
prevInputTokens?: number;
|
|
159
161
|
/** Last-known context usage percentage (0–100), captured at completion. */
|
|
160
162
|
contextPercent?: number | null;
|
|
161
163
|
}
|
|
@@ -26,6 +26,7 @@ function buildStatConfig(store: ReturnType<typeof getStore>) {
|
|
|
26
26
|
["showTools", { label: "Show tools", get: () => store.agent.showTools, set: (v) => store.mutate.agent.setShowTools(v) }],
|
|
27
27
|
["showTurns", { label: "Show turns", get: () => store.agent.showTurns, set: (v) => store.mutate.agent.setShowTurns(v) }],
|
|
28
28
|
["showInput", { label: "Show input tokens", get: () => store.agent.showInput, set: (v) => store.mutate.agent.setShowInput(v) }],
|
|
29
|
+
["deltaInputTokens", { label: "Delta input tokens", get: () => store.agent.deltaInputTokens, set: (v) => store.mutate.agent.setDeltaInputTokens(v) }],
|
|
29
30
|
["showOutput", { label: "Show output tokens", get: () => store.agent.showOutput, set: (v) => store.mutate.agent.setShowOutput(v) }],
|
|
30
31
|
["showContext", { label: "Show context %", get: () => store.agent.showContext, set: (v) => store.mutate.agent.setShowContext(v) }],
|
|
31
32
|
["showCost", { label: "Show cost", get: () => store.agent.showCost, set: (v) => store.mutate.agent.setShowCost(v) }],
|
|
@@ -66,6 +67,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
|
|
|
66
67
|
showTools: "Show tool count (🛠 ) in the widget.",
|
|
67
68
|
showTurns: "Show turn count (⟳ ) in the widget.",
|
|
68
69
|
showInput: "Show input tokens (↑) in the widget.",
|
|
70
|
+
deltaInputTokens: "Estimate input token delta for vLLM (no cache reporting).",
|
|
69
71
|
showOutput: "Show output tokens (↓) in the widget.",
|
|
70
72
|
showContext: "Show context-fill percent (%) in the widget.",
|
|
71
73
|
showCost: "Show dollar cost ($) in the widget.",
|