@mono-agent/agent-runtime 0.20.10 → 0.20.14
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/MIGRATION.md +264 -25
- package/README.md +139 -448
- package/package.json +7 -43
- package/src/agent/tools/agent-tool.js +5 -5
- package/src/agent/tools/bash.js +1 -24
- package/src/agent/tools/codex-subscription-search.js +1 -1
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +140 -0
- package/src/agent/tools/pi-bridge.js +92 -7
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +274 -0
- package/src/agent/tools/shared/process-runner.js +23 -5
- package/src/ai/cost.js +13 -68
- package/src/ai/index.js +4 -17
- package/src/ai/pi-interop.js +65 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +49 -21
- package/src/ai/providers/pi-native/harness-adapter.js +338 -0
- package/src/ai/providers/pi-native/result-builder.js +10 -10
- package/src/ai/providers/pi-native/session-lifecycle.js +95 -40
- package/src/ai/providers/pi-native/stream-subscriber.js +23 -5
- package/src/ai/providers/pi-native/turn-runner.js +34 -15
- package/src/ai/providers/pi-native.js +48 -22
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +48 -408
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/types.js +12 -108
- package/src/index.js +0 -6
- package/src/runtime.js +8 -14
- package/types/agent/tools/agent-tool.d.ts +1 -2
- package/types/agent/tools/codex-subscription-search.d.ts +1 -1
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +44 -0
- package/types/agent/tools/pi-bridge.d.ts +3 -2
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +83 -0
- package/types/agent/tools/shared/process-runner.d.ts +11 -2
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +1 -4
- package/types/ai/pi-interop.d.ts +20 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +56 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +3 -3
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +2 -1
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/turn-runner.d.ts +33 -6
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/types.d.ts +28 -250
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// on the caller-owned runState.compaction.
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
|
-
|
|
27
|
+
BACKGROUND_CONTEXT,
|
|
28
28
|
calculateContextTokens,
|
|
29
29
|
compact as compactPreparedContext,
|
|
30
30
|
estimateContextTokens,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
parseContextLimitFromError,
|
|
46
46
|
} from "../pi-errors.js";
|
|
47
47
|
import { appendStructuredOutputInstruction } from "./structured-output.js";
|
|
48
|
+
import { buildPiSessionContext } from "./harness-adapter.js";
|
|
48
49
|
import { runHarnessPrompt } from "./turn-runner.js";
|
|
49
50
|
|
|
50
51
|
// Per-process cache of real context-window ceilings discovered from overflow
|
|
@@ -157,9 +158,10 @@ async function estimateSessionMessageTokens(session) {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
function estimateBuiltContextTokens(branchEntries) {
|
|
161
|
+
async function estimateBuiltContextTokens(branchEntries) {
|
|
161
162
|
try {
|
|
162
|
-
|
|
163
|
+
const messages = buildPiSessionContext(branchEntries);
|
|
164
|
+
return messages.reduce(
|
|
163
165
|
(total, message) => total + (Number(estimateTokens(message)) || 0),
|
|
164
166
|
0,
|
|
165
167
|
);
|
|
@@ -190,16 +192,17 @@ export function piSummaryReserveTokens(summaryMaxTokens, isSplitTurn) {
|
|
|
190
192
|
return reserve;
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
function previewCompactedContext(branchEntries, result) {
|
|
195
|
+
async function previewCompactedContext(branchEntries, result) {
|
|
194
196
|
const previewEntry = {
|
|
195
197
|
type: "compaction",
|
|
196
198
|
id: "mono-agent-compaction-preview",
|
|
197
199
|
parentId: branchEntries.at(-1)?.id || null,
|
|
198
|
-
timestamp:
|
|
200
|
+
timestamp: Date.now(),
|
|
199
201
|
summary: result.summary,
|
|
200
|
-
|
|
202
|
+
retainedTail: result.retainedTail,
|
|
201
203
|
tokensBefore: result.tokensBefore,
|
|
202
204
|
details: result.details,
|
|
205
|
+
usage: result.usage,
|
|
203
206
|
fromHook: true,
|
|
204
207
|
};
|
|
205
208
|
return estimateBuiltContextTokens([...branchEntries, previewEntry]);
|
|
@@ -273,7 +276,7 @@ export async function tryCompact(harness, {
|
|
|
273
276
|
model,
|
|
274
277
|
});
|
|
275
278
|
let effectivePolicy = policy || {};
|
|
276
|
-
/** @type {null | {kind: string, tokensBefore?: number|null, tokensAfter?: number|null, savings?: number|null, error?: any}} */
|
|
279
|
+
/** @type {null | {kind: string, tokensBefore?: number|null, tokensAfter?: number|null, savings?: number|null, firstKeptEntryId?: string|null, error?: any}} */
|
|
277
280
|
let hookDecision = null;
|
|
278
281
|
let removeHook = null;
|
|
279
282
|
try {
|
|
@@ -281,16 +284,20 @@ export async function tryCompact(harness, {
|
|
|
281
284
|
contextWindow: typeof harness?.getModel === "function" ? harness.getModel()?.contextWindow : undefined,
|
|
282
285
|
});
|
|
283
286
|
effectivePolicy = { ...adaptivePolicy, ...(policy || {}) };
|
|
287
|
+
const compactionSettings = {
|
|
288
|
+
enabled: true,
|
|
289
|
+
reserveTokens: piSummaryReserveTokens(effectivePolicy.summaryMaxTokens, false),
|
|
290
|
+
keepRecentTokens: effectivePolicy.keepRecentTokens,
|
|
291
|
+
};
|
|
292
|
+
if (typeof harness?.setCompactionSettings === "function") {
|
|
293
|
+
await harness.setCompactionSettings(compactionSettings);
|
|
294
|
+
}
|
|
284
295
|
if (typeof harness?.on !== "function") {
|
|
285
296
|
throw new Error("Pi AgentHarness does not expose session_before_compact hooks");
|
|
286
297
|
}
|
|
287
298
|
removeHook = harness.on("session_before_compact", async (event) => {
|
|
288
299
|
try {
|
|
289
|
-
let settings =
|
|
290
|
-
enabled: true,
|
|
291
|
-
reserveTokens: piSummaryReserveTokens(effectivePolicy.summaryMaxTokens, false),
|
|
292
|
-
keepRecentTokens: effectivePolicy.keepRecentTokens,
|
|
293
|
-
};
|
|
300
|
+
let settings = compactionSettings;
|
|
294
301
|
let prepared = prepareCompaction(event.branchEntries, settings);
|
|
295
302
|
if (prepared.ok === false) {
|
|
296
303
|
hookDecision = { kind: "failed", error: prepared.error };
|
|
@@ -320,16 +327,22 @@ export async function tryCompact(harness, {
|
|
|
320
327
|
harness.models,
|
|
321
328
|
harness.getModel(),
|
|
322
329
|
event.customInstructions,
|
|
323
|
-
event.signal,
|
|
324
330
|
typeof harness.getThinkingLevel === "function" ? harness.getThinkingLevel() : undefined,
|
|
331
|
+
undefined,
|
|
332
|
+
undefined,
|
|
333
|
+
event.context || BACKGROUND_CONTEXT,
|
|
325
334
|
);
|
|
326
335
|
if (compacted.ok === false) {
|
|
327
336
|
hookDecision = { kind: "failed", error: compacted.error };
|
|
328
337
|
return { cancel: true };
|
|
329
338
|
}
|
|
330
|
-
const tokensBefore = estimateBuiltContextTokens(event.branchEntries);
|
|
331
|
-
const tokensAfter = previewCompactedContext(event.branchEntries, compacted.value);
|
|
339
|
+
const tokensBefore = await estimateBuiltContextTokens(event.branchEntries);
|
|
340
|
+
const tokensAfter = await previewCompactedContext(event.branchEntries, compacted.value);
|
|
332
341
|
const savings = tokensBefore === null || tokensAfter === null ? null : tokensBefore - tokensAfter;
|
|
342
|
+
const firstRetainedMessage = prepared.value.retainedTail[0];
|
|
343
|
+
const firstKeptEntryId = firstRetainedMessage
|
|
344
|
+
? event.branchEntries.find((entry) => entry?.type === "message" && entry.message === firstRetainedMessage)?.id || null
|
|
345
|
+
: null;
|
|
333
346
|
if (savings === null || savings <= 0) {
|
|
334
347
|
hookDecision = { kind: "not_reducible", tokensBefore, tokensAfter, savings };
|
|
335
348
|
return { cancel: true };
|
|
@@ -338,7 +351,7 @@ export async function tryCompact(harness, {
|
|
|
338
351
|
hookDecision = { kind: "insufficient_savings", tokensBefore, tokensAfter, savings };
|
|
339
352
|
return { cancel: true };
|
|
340
353
|
}
|
|
341
|
-
hookDecision = { kind: "accepted", tokensBefore, tokensAfter, savings };
|
|
354
|
+
hookDecision = { kind: "accepted", tokensBefore, tokensAfter, savings, firstKeptEntryId };
|
|
342
355
|
return { compaction: compacted.value };
|
|
343
356
|
} catch (error) {
|
|
344
357
|
hookDecision = { kind: "failed", error };
|
|
@@ -379,7 +392,10 @@ export async function tryCompact(harness, {
|
|
|
379
392
|
model: model || null,
|
|
380
393
|
tokens_before: tokensBefore,
|
|
381
394
|
summary: result?.summary || "",
|
|
382
|
-
|
|
395
|
+
// Pi 0.85 returns retained messages rather than their entry ids. The
|
|
396
|
+
// preparation still retains object identity, so preserve this legacy
|
|
397
|
+
// diagnostic when its first retained message came from a real entry.
|
|
398
|
+
first_kept_entry_id: hookDecision?.firstKeptEntryId || null,
|
|
383
399
|
status: "succeeded",
|
|
384
400
|
created_at: Date.now(),
|
|
385
401
|
});
|
|
@@ -446,12 +462,15 @@ export async function tryCompact(harness, {
|
|
|
446
462
|
: err;
|
|
447
463
|
const message = effectiveError?.message || String(effectiveError);
|
|
448
464
|
const code = effectiveError?.code;
|
|
449
|
-
const
|
|
465
|
+
const tag = effectiveError?._tag;
|
|
466
|
+
const nothingToCompact = tag === "NothingToCompact"
|
|
467
|
+
|| (code === "compaction" && /nothing to compact/i.test(message));
|
|
468
|
+
const busy = tag === "LaneBusy" || code === "busy";
|
|
450
469
|
const warningKind = nothingToCompact
|
|
451
470
|
? "context_compaction_nothing_to_compact"
|
|
452
471
|
: code === "auth"
|
|
453
472
|
? "context_compaction_auth_failed"
|
|
454
|
-
:
|
|
473
|
+
: busy
|
|
455
474
|
? "context_compaction_busy"
|
|
456
475
|
: "context_compaction_failed";
|
|
457
476
|
runtimeWarnings?.push({ warning_kind: warningKind, source: "pi", trigger, message });
|
|
@@ -464,7 +483,7 @@ export async function tryCompact(harness, {
|
|
|
464
483
|
? "nothing_to_compact"
|
|
465
484
|
: code === "auth"
|
|
466
485
|
? "authentication"
|
|
467
|
-
:
|
|
486
|
+
: busy
|
|
468
487
|
? "busy"
|
|
469
488
|
: code === "aborted"
|
|
470
489
|
? "cancelled"
|
|
@@ -474,7 +493,7 @@ export async function tryCompact(harness, {
|
|
|
474
493
|
: {
|
|
475
494
|
message: code === "auth"
|
|
476
495
|
? "Compaction authentication failed."
|
|
477
|
-
:
|
|
496
|
+
: busy
|
|
478
497
|
? "Context was busy and could not be compacted."
|
|
479
498
|
: code === "aborted"
|
|
480
499
|
? "Compaction was cancelled."
|
|
@@ -484,6 +503,15 @@ export async function tryCompact(harness, {
|
|
|
484
503
|
return { applied: false, tokensBefore: null, tokensAfter: null, reduced: null, nothingToCompact };
|
|
485
504
|
} finally {
|
|
486
505
|
removeHook?.();
|
|
506
|
+
if (typeof harness?.setCompactionSettings === "function") {
|
|
507
|
+
try {
|
|
508
|
+
await harness.setCompactionSettings({
|
|
509
|
+
enabled: false,
|
|
510
|
+
reserveTokens: 16_384,
|
|
511
|
+
keepRecentTokens: 20_000,
|
|
512
|
+
});
|
|
513
|
+
} catch { /* best-effort */ }
|
|
514
|
+
}
|
|
487
515
|
}
|
|
488
516
|
}
|
|
489
517
|
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Compatibility boundary between mono-agent's Pi-native bridge and the
|
|
3
|
+
// lane-based AgentHarness API introduced in pi-agent-core 0.85.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AgentHarness,
|
|
7
|
+
BACKGROUND_CONTEXT,
|
|
8
|
+
createBranchSummaryMessage,
|
|
9
|
+
createCompactionSummaryMessage,
|
|
10
|
+
getOrThrow,
|
|
11
|
+
} from "@earendil-works/pi-agent-core";
|
|
12
|
+
|
|
13
|
+
export const PI_CONTEXT = BACKGROUND_CONTEXT;
|
|
14
|
+
|
|
15
|
+
function operationError(record, fallback) {
|
|
16
|
+
const error = new Error(record?.error?.message || fallback);
|
|
17
|
+
if (record?.error?.code) /** @type {any} */ (error).code = record.error.code;
|
|
18
|
+
return error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isContextMessage(message) {
|
|
22
|
+
return message?.role !== "assistant"
|
|
23
|
+
|| !["error", "aborted", "deferred"].includes(message.stopReason);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pi 0.85 no longer exports its session-context projector. Reproduce the
|
|
28
|
+
* public entry contract here so transcript accounting uses the same latest-
|
|
29
|
+
* compaction and failed-assistant filtering rules as the harness.
|
|
30
|
+
* @param {any[]} pathEntries
|
|
31
|
+
* @param {{includeFailed?: boolean}} [options]
|
|
32
|
+
*/
|
|
33
|
+
export function buildPiSessionContext(pathEntries, { includeFailed = false } = {}) {
|
|
34
|
+
let start = 0;
|
|
35
|
+
for (let index = pathEntries.length - 1; index >= 0; index -= 1) {
|
|
36
|
+
if (pathEntries[index]?.type === "compaction") {
|
|
37
|
+
start = index;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const entries = start > 0 ? pathEntries.slice(start) : pathEntries;
|
|
42
|
+
const messages = [];
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (entry?.type === "message") {
|
|
45
|
+
if (includeFailed || isContextMessage(entry.message)) messages.push(entry.message);
|
|
46
|
+
} else if (entry?.type === "compaction") {
|
|
47
|
+
messages.push(createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp));
|
|
48
|
+
messages.push(...(includeFailed
|
|
49
|
+
? (entry.retainedTail || [])
|
|
50
|
+
: (entry.retainedTail || []).filter(isContextMessage)));
|
|
51
|
+
} else if (entry?.type === "branch_summary" && entry.summary) {
|
|
52
|
+
messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return messages;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Give the surrounding bridge the small session surface it used before Pi
|
|
60
|
+
* moved branch operations onto AgentLane. The raw session remains available to
|
|
61
|
+
* AgentHarness.create(), while all transcript reads and writes use the attached
|
|
62
|
+
* main lane once the harness exists.
|
|
63
|
+
* @param {any} rawSession
|
|
64
|
+
*/
|
|
65
|
+
export function createPiSessionAdapter(rawSession) {
|
|
66
|
+
let lane = null;
|
|
67
|
+
let harness = null;
|
|
68
|
+
let closePromise = null;
|
|
69
|
+
|
|
70
|
+
const requireLane = () => {
|
|
71
|
+
if (!lane) throw new Error("Pi session lane is not attached");
|
|
72
|
+
return lane;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
rawSession,
|
|
77
|
+
get metadata() { return rawSession.metadata; },
|
|
78
|
+
attach(nextHarness, nextLane) {
|
|
79
|
+
harness = nextHarness;
|
|
80
|
+
lane = nextLane;
|
|
81
|
+
},
|
|
82
|
+
async buildContext() {
|
|
83
|
+
const entries = await requireLane().findEntries({ order: "oldestFirst" }, PI_CONTEXT);
|
|
84
|
+
// This bridge-facing transcript includes terminal error/abort messages so
|
|
85
|
+
// result classification can observe them. Pi filters those only when it
|
|
86
|
+
// constructs the next provider request.
|
|
87
|
+
return { messages: buildPiSessionContext(entries, { includeFailed: true }) };
|
|
88
|
+
},
|
|
89
|
+
getEntries() {
|
|
90
|
+
return requireLane().findEntries({ order: "oldestFirst" }, PI_CONTEXT);
|
|
91
|
+
},
|
|
92
|
+
getLeafId() {
|
|
93
|
+
return requireLane().getTipId(PI_CONTEXT);
|
|
94
|
+
},
|
|
95
|
+
appendMessage(message) {
|
|
96
|
+
return requireLane().appendMessage(message, PI_CONTEXT);
|
|
97
|
+
},
|
|
98
|
+
async moveTo(targetId) {
|
|
99
|
+
const result = getOrThrow(await requireLane().navigateTree(
|
|
100
|
+
targetId,
|
|
101
|
+
{ summarize: false },
|
|
102
|
+
PI_CONTEXT,
|
|
103
|
+
));
|
|
104
|
+
const record = result.navigation;
|
|
105
|
+
if (record.status === "failed") throw operationError(record, "Pi session navigation failed");
|
|
106
|
+
if (record.status !== "completed") throw operationError(record, "Pi session navigation was not completed");
|
|
107
|
+
return record.tipId;
|
|
108
|
+
},
|
|
109
|
+
getMetadata() {
|
|
110
|
+
return Promise.resolve(rawSession.metadata);
|
|
111
|
+
},
|
|
112
|
+
close() {
|
|
113
|
+
if (!closePromise) {
|
|
114
|
+
closePromise = (harness
|
|
115
|
+
? harness.close(PI_CONTEXT)
|
|
116
|
+
: rawSession.close(PI_CONTEXT));
|
|
117
|
+
}
|
|
118
|
+
return closePromise;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function adaptTool(tool) {
|
|
124
|
+
return {
|
|
125
|
+
...tool,
|
|
126
|
+
async execute(toolCallId, params, onUpdate, _toolContext, _invocation, context) {
|
|
127
|
+
return tool.execute(toolCallId, params, context.abortSignal, onUpdate);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const FORWARDED_EVENT_TYPES = [
|
|
133
|
+
"run_start",
|
|
134
|
+
"run_resume",
|
|
135
|
+
"run_suspend",
|
|
136
|
+
"operation_abort",
|
|
137
|
+
"run_end",
|
|
138
|
+
"fault",
|
|
139
|
+
"handler_error",
|
|
140
|
+
"turn_start",
|
|
141
|
+
"turn_end",
|
|
142
|
+
"retry_scheduled",
|
|
143
|
+
"retry_start",
|
|
144
|
+
"retry_end",
|
|
145
|
+
"message_start",
|
|
146
|
+
"message_update",
|
|
147
|
+
"message_end",
|
|
148
|
+
"tool_start",
|
|
149
|
+
"tool_update",
|
|
150
|
+
"tool_end",
|
|
151
|
+
"entry_added",
|
|
152
|
+
"queue_update",
|
|
153
|
+
"value_update",
|
|
154
|
+
"config_update",
|
|
155
|
+
"compaction_start",
|
|
156
|
+
"compaction_end",
|
|
157
|
+
"navigation_start",
|
|
158
|
+
"navigation_end",
|
|
159
|
+
"lane_created",
|
|
160
|
+
"usage",
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
function legacyEvent(event) {
|
|
164
|
+
// appendMessage() emits runless lifecycle events for transcript seeding.
|
|
165
|
+
// They are persistence notifications, not output from the active provider
|
|
166
|
+
// run, and forwarding them would manufacture assistant boundaries/usage for
|
|
167
|
+
// prior history. Real run-owned message events always carry runId in Pi 0.85.
|
|
168
|
+
if ((event.type === "message_start" || event.type === "message_end") && !event.runId) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (event.type === "message_update") {
|
|
172
|
+
return { ...event, assistantMessageEvent: event.event };
|
|
173
|
+
}
|
|
174
|
+
if (event.type === "tool_start") return { ...event, type: "tool_execution_start" };
|
|
175
|
+
if (event.type === "tool_update") return { ...event, type: "tool_execution_update" };
|
|
176
|
+
if (event.type === "tool_end") return { ...event, type: "tool_execution_end" };
|
|
177
|
+
return event;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Attach Pi 0.85's harness to one session and expose the intentionally small
|
|
182
|
+
* surface consumed by mono-agent's turn runner.
|
|
183
|
+
* @param {any} session
|
|
184
|
+
* @param {any} options
|
|
185
|
+
*/
|
|
186
|
+
export async function createPiHarnessAdapter(session, options) {
|
|
187
|
+
const originalTools = Array.isArray(options.tools) ? options.tools : [];
|
|
188
|
+
const adaptedTools = originalTools.map(adaptTool);
|
|
189
|
+
const activeToolNames = originalTools.map((tool) => tool.name);
|
|
190
|
+
// Pi 0.85 removed mixed per-tool scheduling from AgentHarness: its runner
|
|
191
|
+
// consults only this global setting. Preserve the safety contract by
|
|
192
|
+
// serializing the batch whenever any offered tool is stateful, mutating, or
|
|
193
|
+
// MCP-backed. Read-only-only tool sets can still overlap in safe-parallel.
|
|
194
|
+
const toolExecution = originalTools.some((tool) => tool?.executionMode === "sequential")
|
|
195
|
+
? "sequential"
|
|
196
|
+
: "parallel";
|
|
197
|
+
|
|
198
|
+
/** @type {any} */
|
|
199
|
+
let created;
|
|
200
|
+
/** @type {any} */
|
|
201
|
+
let rawHarness;
|
|
202
|
+
/** @type {any} */
|
|
203
|
+
let lane;
|
|
204
|
+
try {
|
|
205
|
+
created = await AgentHarness.create({
|
|
206
|
+
...options,
|
|
207
|
+
session: session.rawSession,
|
|
208
|
+
tools: adaptedTools,
|
|
209
|
+
activeToolNames,
|
|
210
|
+
toolExecution,
|
|
211
|
+
// mono-agent owns proactive/reactive compaction policy. The permanent hook
|
|
212
|
+
// below also declines Pi's overflow recovery so one bridge never runs two
|
|
213
|
+
// competing policies.
|
|
214
|
+
compaction: { enabled: false, reserveTokens: 16_384, keepRecentTokens: 20_000 },
|
|
215
|
+
}, PI_CONTEXT);
|
|
216
|
+
rawHarness = created.harness;
|
|
217
|
+
// Attach the harness before lane/configuration awaits so any later failure
|
|
218
|
+
// can close the partially constructed handle instead of wedging the repo.
|
|
219
|
+
session.attach(rawHarness, null);
|
|
220
|
+
lane = await rawHarness.lane("main", PI_CONTEXT);
|
|
221
|
+
session.attach(rawHarness, lane);
|
|
222
|
+
|
|
223
|
+
// A restored lane carries its prior configuration, so explicitly bind it to
|
|
224
|
+
// this run's model, effort, and available tools. Legacy v3 transcripts import
|
|
225
|
+
// with an empty active-tool list and are upgraded atomically by Pi on write.
|
|
226
|
+
await rawHarness.setTools(adaptedTools, PI_CONTEXT);
|
|
227
|
+
await lane.setModel({ provider: options.model.provider, modelId: options.model.id }, PI_CONTEXT);
|
|
228
|
+
await lane.setThinkingLevel(options.thinkingLevel ?? "off", PI_CONTEXT);
|
|
229
|
+
await lane.setActiveTools(activeToolNames, PI_CONTEXT);
|
|
230
|
+
|
|
231
|
+
rawHarness.hooks.on("before_compaction", (event) => (
|
|
232
|
+
event.reason === "manual" ? undefined : { decline: true }
|
|
233
|
+
), { id: "mono-agent-compaction-owner" });
|
|
234
|
+
} catch (error) {
|
|
235
|
+
try { await session.close(); } catch { /* preserve the construction error */ }
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let closed = false;
|
|
240
|
+
let currentModel = options.model;
|
|
241
|
+
let currentThinkingLevel = options.thinkingLevel ?? "off";
|
|
242
|
+
let currentActiveToolNames = [...activeToolNames];
|
|
243
|
+
const manuallyAppendedEntryIds = new Set();
|
|
244
|
+
|
|
245
|
+
const adapter = {
|
|
246
|
+
models: options.models,
|
|
247
|
+
getModel: () => currentModel,
|
|
248
|
+
getThinkingLevel: () => currentThinkingLevel,
|
|
249
|
+
getActiveTools: () => originalTools.filter((tool) => currentActiveToolNames.includes(tool.name)),
|
|
250
|
+
async setActiveTools(names) {
|
|
251
|
+
await lane.setActiveTools(names, PI_CONTEXT);
|
|
252
|
+
currentActiveToolNames = [...names];
|
|
253
|
+
},
|
|
254
|
+
async setCompactionSettings(settings) {
|
|
255
|
+
await rawHarness.setCompactionSettings(settings, PI_CONTEXT);
|
|
256
|
+
},
|
|
257
|
+
async appendMessage(message) {
|
|
258
|
+
const entryId = await lane.appendMessage(message, PI_CONTEXT);
|
|
259
|
+
manuallyAppendedEntryIds.add(entryId);
|
|
260
|
+
return entryId;
|
|
261
|
+
},
|
|
262
|
+
async prompt(text, promptOptions) {
|
|
263
|
+
return getOrThrow(await lane.prompt(text, promptOptions?.images, PI_CONTEXT));
|
|
264
|
+
},
|
|
265
|
+
async steer(message) {
|
|
266
|
+
return getOrThrow(await lane.steer(message, undefined, PI_CONTEXT));
|
|
267
|
+
},
|
|
268
|
+
async abort() {
|
|
269
|
+
const result = await lane.abort(PI_CONTEXT);
|
|
270
|
+
// Aborting an already-idle lane is a benign race with prompt settlement.
|
|
271
|
+
if (!result.ok) {
|
|
272
|
+
const error = /** @type {{error: any}} */ (result).error;
|
|
273
|
+
if (error?._tag !== "NoActiveOperation") throw error;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
waitForIdle() {
|
|
277
|
+
return lane.waitForIdle(PI_CONTEXT);
|
|
278
|
+
},
|
|
279
|
+
async compact() {
|
|
280
|
+
const value = getOrThrow(await lane.compact(undefined, PI_CONTEXT));
|
|
281
|
+
const record = value.compaction;
|
|
282
|
+
if (record.status === "failed") throw operationError(record, "Pi compaction failed");
|
|
283
|
+
if (record.status !== "completed") throw operationError(record, "Pi compaction cancelled");
|
|
284
|
+
const entry = record.tipId
|
|
285
|
+
? await session.rawSession.getEntry(record.tipId, PI_CONTEXT)
|
|
286
|
+
: undefined;
|
|
287
|
+
if (!entry || entry.type !== "compaction") {
|
|
288
|
+
throw new Error("Pi compaction completed without a compaction entry");
|
|
289
|
+
}
|
|
290
|
+
return entry;
|
|
291
|
+
},
|
|
292
|
+
on(type, handler) {
|
|
293
|
+
if (type === "tool_result") {
|
|
294
|
+
return rawHarness.hooks.on("after_tool", (event) => handler(event));
|
|
295
|
+
}
|
|
296
|
+
if (type === "session_before_compact") {
|
|
297
|
+
return rawHarness.hooks.on("before_compaction", async (event, context) => {
|
|
298
|
+
const branchEntries = await lane.findEntries({ order: "oldestFirst" }, context);
|
|
299
|
+
const result = await handler({
|
|
300
|
+
...event,
|
|
301
|
+
branchEntries,
|
|
302
|
+
signal: context.abortSignal,
|
|
303
|
+
context,
|
|
304
|
+
});
|
|
305
|
+
if (result?.cancel) return { decline: true };
|
|
306
|
+
return result?.compaction === undefined ? undefined : { compaction: result.compaction };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
throw new Error(`Unsupported Pi harness hook: ${String(type)}`);
|
|
310
|
+
},
|
|
311
|
+
subscribe(listener) {
|
|
312
|
+
const removes = FORWARDED_EVENT_TYPES.map((type) => rawHarness.events.on(
|
|
313
|
+
/** @type {any} */ (type),
|
|
314
|
+
(event) => {
|
|
315
|
+
if (event.type === "message_end" && manuallyAppendedEntryIds.has(event.entryId)) return;
|
|
316
|
+
const converted = legacyEvent(event);
|
|
317
|
+
if (converted) listener(converted);
|
|
318
|
+
},
|
|
319
|
+
));
|
|
320
|
+
return () => removes.forEach((remove) => remove());
|
|
321
|
+
},
|
|
322
|
+
async abortOpenOperations() {
|
|
323
|
+
if (!created.open.some((operation) => operation.lane === "main")) return;
|
|
324
|
+
// mono-agent tools predate Pi's invocation memo/checkpoint API, so replaying
|
|
325
|
+
// an interrupted durable operation could repeat an external side effect.
|
|
326
|
+
// Fail closed by settling it as aborted before admitting a new prompt.
|
|
327
|
+
getOrThrow(await lane.abort(PI_CONTEXT));
|
|
328
|
+
await lane.waitForIdle(PI_CONTEXT);
|
|
329
|
+
},
|
|
330
|
+
async close() {
|
|
331
|
+
if (closed) return;
|
|
332
|
+
closed = true;
|
|
333
|
+
await session.close();
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return adapter;
|
|
338
|
+
}
|
|
@@ -118,14 +118,14 @@ export function emitUsageCostEvents({
|
|
|
118
118
|
externalAbort,
|
|
119
119
|
}) {
|
|
120
120
|
if (usage.cacheRead > 0) {
|
|
121
|
-
onEvent({ type: "cache_hit", sdk:
|
|
121
|
+
onEvent({ type: "cache_hit", sdk: "pi", model: reference, tokens: usage.cacheRead, source: "prompt_cache" });
|
|
122
122
|
}
|
|
123
123
|
if (usage.cacheWrite > 0) {
|
|
124
|
-
onEvent({ type: "cache_miss", sdk:
|
|
124
|
+
onEvent({ type: "cache_miss", sdk: "pi", model: reference, tokens: usage.cacheWrite, source: "prompt_cache" });
|
|
125
125
|
}
|
|
126
126
|
onEvent({
|
|
127
127
|
type: "cost_accumulated",
|
|
128
|
-
sdk:
|
|
128
|
+
sdk: "pi",
|
|
129
129
|
model: reference,
|
|
130
130
|
cumulativeUsd: Number(usage.cost) || Number(estimatedCost) || 0,
|
|
131
131
|
tokens: {
|
|
@@ -137,7 +137,7 @@ export function emitUsageCostEvents({
|
|
|
137
137
|
});
|
|
138
138
|
onEvent({
|
|
139
139
|
type: "provider_request_completed",
|
|
140
|
-
sdk:
|
|
140
|
+
sdk: "pi",
|
|
141
141
|
model: reference,
|
|
142
142
|
runtime: "pi",
|
|
143
143
|
timestamp: Date.now(),
|
|
@@ -167,9 +167,9 @@ export function abortedResult({ resolved, options, events, runtimeWarnings, star
|
|
|
167
167
|
usage: {},
|
|
168
168
|
durationMs: Date.now() - start,
|
|
169
169
|
numTurns: 0,
|
|
170
|
-
model: resolved?.reference || resolved?.model
|
|
170
|
+
model: resolved?.reference || (resolved?.provider && resolved?.model ? `${resolved.provider}:${resolved.model}` : null),
|
|
171
171
|
effort: options.effort || null,
|
|
172
|
-
sdk:
|
|
172
|
+
sdk: "pi",
|
|
173
173
|
cancelled: true,
|
|
174
174
|
error: null,
|
|
175
175
|
failureKind: null,
|
|
@@ -226,9 +226,9 @@ export function buildSuccessResult(params) {
|
|
|
226
226
|
},
|
|
227
227
|
durationMs: Date.now() - start,
|
|
228
228
|
numTurns: turnCount || runAssistantCount,
|
|
229
|
-
model: resolved.reference ||
|
|
229
|
+
model: resolved.reference || `${resolved.provider}:${resolved.model}`,
|
|
230
230
|
effort: options.effort || null,
|
|
231
|
-
sdk:
|
|
231
|
+
sdk: "pi",
|
|
232
232
|
cancelled: externalAbort,
|
|
233
233
|
error: errorMessage,
|
|
234
234
|
errorDetails,
|
|
@@ -273,9 +273,9 @@ export function buildErrorResult(params) {
|
|
|
273
273
|
usage: {},
|
|
274
274
|
durationMs: Date.now() - start,
|
|
275
275
|
numTurns: turnCount,
|
|
276
|
-
model: resolved?.reference || resolved?.model
|
|
276
|
+
model: resolved?.reference || (resolved?.provider && resolved?.model ? `${resolved.provider}:${resolved.model}` : null),
|
|
277
277
|
effort: options.effort || null,
|
|
278
|
-
sdk:
|
|
278
|
+
sdk: "pi",
|
|
279
279
|
cancelled: externalAbort,
|
|
280
280
|
error: externalAbort ? null : errorMessage,
|
|
281
281
|
errorDetails: externalAbort ? null : {
|