@danypops/pi-jittor 0.7.0 → 0.8.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/extension/src/index.ts +123 -152
- package/package.json +2 -2
package/extension/src/index.ts
CHANGED
|
@@ -164,21 +164,6 @@ async function refreshFooter(client: JittorExtensionClient, state: IntegratedFoo
|
|
|
164
164
|
state.requestRender?.();
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
|
|
168
|
-
if (milliseconds <= 0) return Promise.resolve();
|
|
169
|
-
return new Promise((resolve, reject) => {
|
|
170
|
-
const timer = setTimeout(resolve, milliseconds);
|
|
171
|
-
signal?.addEventListener(
|
|
172
|
-
"abort",
|
|
173
|
-
() => {
|
|
174
|
-
clearTimeout(timer);
|
|
175
|
-
reject(new Error("Jittor throttle cancelled"));
|
|
176
|
-
},
|
|
177
|
-
{ once: true },
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
167
|
function routeModelAvailable(ctx: ExtensionContext, route: Route): boolean {
|
|
183
168
|
return ctx.modelRegistry.getAvailable().some((model) => model.provider === route.provider && model.id === route.model);
|
|
184
169
|
}
|
|
@@ -318,38 +303,6 @@ async function syncCurrentRoute(
|
|
|
318
303
|
});
|
|
319
304
|
}
|
|
320
305
|
|
|
321
|
-
function halt(ctx: ExtensionContext, reason: string): false {
|
|
322
|
-
ctx.ui.notify(`${reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
323
|
-
ctx.abort();
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async function applyDecision(
|
|
328
|
-
pi: ExtensionAPI,
|
|
329
|
-
client: JittorExtensionClient,
|
|
330
|
-
ctx: ExtensionContext,
|
|
331
|
-
decision: PolicyDecision,
|
|
332
|
-
allowResync = true,
|
|
333
|
-
): Promise<boolean> {
|
|
334
|
-
if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
|
|
335
|
-
if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
|
|
336
|
-
if (!decision.route || (await applyRoute(pi, ctx, decision.route))) return true;
|
|
337
|
-
if (allowResync) {
|
|
338
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
339
|
-
return applyDecision(
|
|
340
|
-
pi,
|
|
341
|
-
client,
|
|
342
|
-
ctx,
|
|
343
|
-
(await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
|
|
344
|
-
false,
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
return halt(
|
|
348
|
-
ctx,
|
|
349
|
-
`Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`,
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
306
|
let assistantUsageRunSequence = 0;
|
|
354
307
|
|
|
355
308
|
/**
|
|
@@ -503,54 +456,61 @@ export function registerJittorExtension(
|
|
|
503
456
|
const contextFingerprinter = new HmacContextFingerprinter(contextFingerprintKey);
|
|
504
457
|
let contextCaptureSequence = 0;
|
|
505
458
|
pi.on("before_provider_request", (event, ctx) => {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
activeEntryIds: Set<string>;
|
|
512
|
-
branchEntryIds: Set<string>;
|
|
513
|
-
}
|
|
514
|
-
| undefined;
|
|
459
|
+
// Pi awaits this hook immediately before opening the provider request. Snapshotting a large
|
|
460
|
+
// payload here used to add its full CPU cost to TTFT even though persistence was detached.
|
|
461
|
+
// Defer the entire observational path to a later macrotask; the provider payload is immutable
|
|
462
|
+
// after serialization in Pi's request path, and a missed snapshot is preferable to delaying it.
|
|
463
|
+
const timer = setTimeout(() => {
|
|
515
464
|
try {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
465
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
466
|
+
let history:
|
|
467
|
+
| {
|
|
468
|
+
roots: SessionTreeNodeLike[];
|
|
469
|
+
activeEntryIds: Set<string>;
|
|
470
|
+
branchEntryIds: Set<string>;
|
|
471
|
+
}
|
|
472
|
+
| undefined;
|
|
473
|
+
try {
|
|
474
|
+
history = {
|
|
475
|
+
roots: ctx.sessionManager.getTree() as SessionTreeNodeLike[],
|
|
476
|
+
activeEntryIds: new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id)),
|
|
477
|
+
branchEntryIds: new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id)),
|
|
478
|
+
};
|
|
479
|
+
} catch {
|
|
480
|
+
// Older/custom SessionManager implementations may not expose tree projections.
|
|
481
|
+
}
|
|
482
|
+
const captureInput = {
|
|
483
|
+
payload: event.payload,
|
|
484
|
+
captureId: `${++contextCaptureSequence}`,
|
|
485
|
+
sessionId,
|
|
486
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
487
|
+
model: ctx.model?.id ?? "unknown",
|
|
488
|
+
capturedAt: Date.now(),
|
|
489
|
+
fingerprinter: contextFingerprinter,
|
|
520
490
|
};
|
|
491
|
+
let snapshot: ContextSnapshot;
|
|
492
|
+
try {
|
|
493
|
+
snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
|
|
494
|
+
} catch {
|
|
495
|
+
// A custom SessionManager tree shape must not suppress the real request-payload snapshot.
|
|
496
|
+
snapshot = captureProviderContextSnapshot(captureInput);
|
|
497
|
+
}
|
|
498
|
+
// Observation must never alter or abort the provider request. Both local writes are detached;
|
|
499
|
+
// they receive only bounded token sizes and keyed fingerprints.
|
|
500
|
+
const requestTokens = snapshot.segments
|
|
501
|
+
.filter((segment) => segment.requestPosition !== null)
|
|
502
|
+
.reduce((sum, segment) => sum + segment.tokens, 0);
|
|
503
|
+
const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
|
|
504
|
+
provider: snapshot.provider,
|
|
505
|
+
model: snapshot.model,
|
|
506
|
+
});
|
|
507
|
+
void client.call("context.snapshot", snapshot).catch(() => undefined);
|
|
508
|
+
if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
|
|
521
509
|
} catch {
|
|
522
|
-
//
|
|
523
|
-
}
|
|
524
|
-
const captureInput = {
|
|
525
|
-
payload: event.payload,
|
|
526
|
-
captureId: `${++contextCaptureSequence}`,
|
|
527
|
-
sessionId,
|
|
528
|
-
provider: ctx.model?.provider ?? "unknown",
|
|
529
|
-
model: ctx.model?.id ?? "unknown",
|
|
530
|
-
capturedAt: Date.now(),
|
|
531
|
-
fingerprinter: contextFingerprinter,
|
|
532
|
-
};
|
|
533
|
-
let snapshot: ContextSnapshot;
|
|
534
|
-
try {
|
|
535
|
-
snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
|
|
536
|
-
} catch {
|
|
537
|
-
// A custom SessionManager tree shape must not suppress the real request-payload snapshot.
|
|
538
|
-
snapshot = captureProviderContextSnapshot(captureInput);
|
|
510
|
+
// Snapshot collection is strictly failure-isolated from provider delivery.
|
|
539
511
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const requestTokens = snapshot.segments
|
|
543
|
-
.filter((segment) => segment.requestPosition !== null)
|
|
544
|
-
.reduce((sum, segment) => sum + segment.tokens, 0);
|
|
545
|
-
const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
|
|
546
|
-
provider: snapshot.provider,
|
|
547
|
-
model: snapshot.model,
|
|
548
|
-
});
|
|
549
|
-
void client.call("context.snapshot", snapshot).catch(() => undefined);
|
|
550
|
-
if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
|
|
551
|
-
} catch {
|
|
552
|
-
// Snapshot collection is strictly failure-isolated from provider delivery.
|
|
553
|
-
}
|
|
512
|
+
}, 0);
|
|
513
|
+
timer.unref?.();
|
|
554
514
|
});
|
|
555
515
|
const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
|
|
556
516
|
// Cached from the most recent before_agent_start observation: Pi's own base system prompt is
|
|
@@ -1002,25 +962,58 @@ export function registerJittorExtension(
|
|
|
1002
962
|
]).catch(() => undefined);
|
|
1003
963
|
});
|
|
1004
964
|
|
|
1005
|
-
|
|
965
|
+
let ambientRouting: Promise<void> | null = null;
|
|
966
|
+
const scheduleAmbientRouting = (ctx: ExtensionContext): void => {
|
|
967
|
+
if (ambientRouting || !ctx.isIdle()) return;
|
|
968
|
+
ambientRouting = (async () => {
|
|
969
|
+
try {
|
|
970
|
+
await syncCurrentRoute(pi, client, ctx);
|
|
971
|
+
await syncAvailableRoutes(pi, client, ctx);
|
|
972
|
+
if (!ctx.isIdle()) return;
|
|
973
|
+
let budgetActed = false;
|
|
974
|
+
if (enforcement.isEnabled()) {
|
|
975
|
+
const decision = (await client.call("router.decide", {
|
|
976
|
+
session_id: ctx.sessionManager.getSessionId(),
|
|
977
|
+
})) as PolicyDecision;
|
|
978
|
+
if (!ctx.isIdle()) return;
|
|
979
|
+
budgetActed = decision.action !== "continue";
|
|
980
|
+
if (decision.action === "halt") {
|
|
981
|
+
ctx.ui.notify(`Jittor recommends pausing future requests: ${decision.reason}.`, "warning");
|
|
982
|
+
} else if (decision.route && !(await applyRoute(pi, ctx, decision.route)) && ctx.isIdle()) {
|
|
983
|
+
// Catalogs can change while an ambient decision is in flight. Refresh and try one
|
|
984
|
+
// newly-computed route; failure simply leaves the current model in place.
|
|
985
|
+
await syncAvailableRoutes(pi, client, ctx);
|
|
986
|
+
const refreshed = (await client.call("router.decide", {
|
|
987
|
+
session_id: ctx.sessionManager.getSessionId(),
|
|
988
|
+
})) as PolicyDecision;
|
|
989
|
+
if (ctx.isIdle() && refreshed.route) await applyRoute(pi, ctx, refreshed.route);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (!budgetActed && ctx.isIdle()) await runAutoModeTurn(ctx);
|
|
993
|
+
} catch {
|
|
994
|
+
// Routing is opportunistic: daemon latency or failure must never enter Pi's request path.
|
|
995
|
+
} finally {
|
|
996
|
+
ambientRouting = null;
|
|
997
|
+
}
|
|
998
|
+
})();
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
1006
1002
|
if (footerState.compaction) {
|
|
1007
1003
|
finishCompactionUi();
|
|
1008
1004
|
if (compactionTelemetry.hasOpenCompaction())
|
|
1009
|
-
|
|
1005
|
+
void recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
|
|
1010
1006
|
}
|
|
1011
1007
|
scheduleCodexRecovery(ctx);
|
|
1008
|
+
scheduleAmbientRouting(ctx);
|
|
1012
1009
|
if (!enforcement.isFooterEnabled()) return;
|
|
1013
|
-
|
|
1014
|
-
await syncCurrentRoute(pi, client, ctx);
|
|
1015
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
1016
|
-
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
1017
|
-
} catch {
|
|
1010
|
+
void refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => {
|
|
1018
1011
|
footerState.providerBudget = null;
|
|
1019
1012
|
footerState.requestRender?.();
|
|
1020
|
-
}
|
|
1013
|
+
});
|
|
1021
1014
|
});
|
|
1022
1015
|
|
|
1023
|
-
pi.on("input",
|
|
1016
|
+
pi.on("input", (event) => {
|
|
1024
1017
|
if (event.source !== "extension") {
|
|
1025
1018
|
cancelRecovery(true);
|
|
1026
1019
|
// Auto mode's own effort classifier reads this at the next turn_start -- captured
|
|
@@ -1028,57 +1021,34 @@ export function registerJittorExtension(
|
|
|
1028
1021
|
// separate, independently-toggled feature from budget enforcement.
|
|
1029
1022
|
pendingUserText = event.text;
|
|
1030
1023
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (next.action === "halt") {
|
|
1035
|
-
ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
1036
|
-
return { action: "handled" as const };
|
|
1037
|
-
}
|
|
1038
|
-
return { action: "continue" as const };
|
|
1039
|
-
} catch {
|
|
1040
|
-
ctx.ui.notify(`Jittor could not verify budget telemetry, so fail-closed enforcement blocked input. ${RECOVERY_GUIDANCE}.`, "error");
|
|
1041
|
-
return { action: "handled" as const };
|
|
1042
|
-
}
|
|
1024
|
+
// Routing is advisory and ambient. The submitted message always enters Pi immediately;
|
|
1025
|
+
// daemon decisions computed while idle can optimize a later turn.
|
|
1026
|
+
return { action: "continue" as const };
|
|
1043
1027
|
});
|
|
1044
1028
|
|
|
1045
|
-
pi.on("model_select",
|
|
1046
|
-
|
|
1029
|
+
pi.on("model_select", (event, ctx) => {
|
|
1030
|
+
void syncCurrentRoute(pi, client, ctx, event.model)
|
|
1047
1031
|
.then(() => syncAvailableRoutes(pi, client, ctx))
|
|
1032
|
+
.then(() =>
|
|
1033
|
+
enforcement.isFooterEnabled()
|
|
1034
|
+
? refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined)
|
|
1035
|
+
: undefined,
|
|
1036
|
+
)
|
|
1048
1037
|
.catch(() => undefined);
|
|
1049
|
-
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
1050
1038
|
});
|
|
1051
1039
|
|
|
1052
|
-
pi.on("thinking_level_select",
|
|
1053
|
-
|
|
1040
|
+
pi.on("thinking_level_select", (event, ctx) => {
|
|
1041
|
+
void syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
|
|
1054
1042
|
});
|
|
1055
1043
|
|
|
1056
|
-
pi.on("turn_start",
|
|
1044
|
+
pi.on("turn_start", (event, ctx) => {
|
|
1057
1045
|
currentSessionId = ctx.sessionManager.getSessionId();
|
|
1058
1046
|
compactionTelemetry.observeTurn();
|
|
1059
1047
|
codexRecoveryCapability.resetTurn();
|
|
1060
1048
|
providerResponseTelemetry.resetTurn();
|
|
1061
1049
|
localRunTelemetry.beginTurn(event.timestamp);
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
// so the two decision sources never fight over the model mid-turn.
|
|
1065
|
-
let budgetActedThisTurn = false;
|
|
1066
|
-
if (enforcement.isEnabled()) {
|
|
1067
|
-
try {
|
|
1068
|
-
await syncCurrentRoute(pi, client, ctx);
|
|
1069
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
1070
|
-
const budgetDecision = (await client.call("router.decide", {
|
|
1071
|
-
session_id: ctx.sessionManager.getSessionId(),
|
|
1072
|
-
})) as PolicyDecision;
|
|
1073
|
-
budgetActedThisTurn = budgetDecision.action !== "continue";
|
|
1074
|
-
await applyDecision(pi, client, ctx, budgetDecision);
|
|
1075
|
-
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
1076
|
-
} catch {
|
|
1077
|
-
halt(ctx, "Jittor could not verify or apply a safe route");
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
if (!budgetActedThisTurn) await runAutoModeTurn(ctx).catch(() => undefined);
|
|
1050
|
+
// Deliberately no daemon or routing work here: Pi awaits turn_start before provider delivery.
|
|
1051
|
+
// The previous agent_settled event computes and applies recommendations for later turns.
|
|
1082
1052
|
});
|
|
1083
1053
|
|
|
1084
1054
|
pi.on("message_update", async (event) => {
|
|
@@ -1095,35 +1065,35 @@ export function registerJittorExtension(
|
|
|
1095
1065
|
if (currentTurnToolNames.length < EFFORT_CLASSIFICATION_MAX_TOOL_NAMES) currentTurnToolNames.push(event.toolName);
|
|
1096
1066
|
});
|
|
1097
1067
|
|
|
1098
|
-
pi.on("after_provider_response",
|
|
1068
|
+
pi.on("after_provider_response", (event, ctx) => {
|
|
1099
1069
|
localRunTelemetry.onProviderResponse();
|
|
1100
1070
|
if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
|
|
1101
1071
|
const notifySchemaDrift = (message: string) => {
|
|
1102
1072
|
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error");
|
|
1103
1073
|
};
|
|
1104
|
-
|
|
1105
|
-
|
|
1074
|
+
// Pi fires this before consuming the provider stream. Never put telemetry or footer RPCs
|
|
1075
|
+
// between the HTTP response and the first streamed token.
|
|
1076
|
+
void providerResponseTelemetry
|
|
1077
|
+
.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift)
|
|
1078
|
+
.catch(() => undefined);
|
|
1106
1079
|
});
|
|
1107
1080
|
|
|
1108
|
-
pi.on("turn_end",
|
|
1081
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1109
1082
|
const tokens = ctx.getContextUsage()?.tokens;
|
|
1110
1083
|
if (typeof tokens === "number" && Number.isFinite(tokens)) contextGrowth.observe(++contextGrowthTurn, tokens);
|
|
1111
1084
|
const metrics = localRunTelemetry.completeTurn(event.message, pi.getThinkingLevel());
|
|
1112
|
-
|
|
1085
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1113
1086
|
priorTurnToolNames = currentTurnToolNames;
|
|
1114
1087
|
currentTurnToolNames = [];
|
|
1115
1088
|
});
|
|
1116
1089
|
|
|
1117
|
-
pi.on("message_end",
|
|
1090
|
+
pi.on("message_end", (event, ctx) => {
|
|
1118
1091
|
if (event.message.role === "assistant") {
|
|
1119
1092
|
if (event.message.provider === "openai-codex")
|
|
1120
1093
|
codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
|
|
1121
|
-
|
|
1122
|
-
client,
|
|
1123
|
-
|
|
1124
|
-
event.message.stopReason,
|
|
1125
|
-
event.message.errorMessage,
|
|
1126
|
-
);
|
|
1094
|
+
void providerResponseTelemetry
|
|
1095
|
+
.handleMessageEnd(client, event.message.provider, event.message.stopReason, event.message.errorMessage)
|
|
1096
|
+
.catch(() => undefined);
|
|
1127
1097
|
}
|
|
1128
1098
|
const metrics = assistantUsageMetrics(
|
|
1129
1099
|
event.message,
|
|
@@ -1143,9 +1113,10 @@ export function registerJittorExtension(
|
|
|
1143
1113
|
cacheRead: amount("cache-read-tokens"),
|
|
1144
1114
|
cacheWrite: amount("cache-write-tokens"),
|
|
1145
1115
|
});
|
|
1146
|
-
|
|
1116
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1147
1117
|
}
|
|
1148
|
-
|
|
1118
|
+
// Footer refresh is consolidated in agent_settled; finalized-message accounting must not
|
|
1119
|
+
// hold back streamed lifecycle delivery or the next queued prompt.
|
|
1149
1120
|
});
|
|
1150
1121
|
|
|
1151
1122
|
pi.on("session_shutdown", async (_event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-jittor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Pi extension for Jittor token and context observability with optimization controls backed by the @danypops/jittor daemon",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@danypops/pi-extension-harness": "^0.6.2",
|
|
29
|
-
"@danypops/pi-process-harness": "^0.
|
|
29
|
+
"@danypops/pi-process-harness": "^0.3.2",
|
|
30
30
|
"@danypops/pi-tui-harness": "^0.0.2",
|
|
31
31
|
"@earendil-works/pi-ai": "*",
|
|
32
32
|
"bun-types": "latest"
|