@danypops/pi-jittor 0.1.0 → 0.1.1
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.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTEXT_HUB_CONTRIBUTION_DEDUP_LIMIT,
|
|
3
|
+
validateContextContribution,
|
|
4
|
+
type ContextSegment,
|
|
5
|
+
} from "@danypops/jittor";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Merges Jittor's own directly-computed segments (tool ledger, real usage) with whatever
|
|
9
|
+
* segments other extensions contributed on CONTEXT_HUB_CONTRIBUTION_CHANNEL for the current
|
|
10
|
+
* session. Keeps only the latest contribution per producer -- a producer re-emits every turn
|
|
11
|
+
* (mirroring Papyrus's own context-injection.v1 cadence), so an older segment from the same
|
|
12
|
+
* producer is stale, not a second real contributor.
|
|
13
|
+
*/
|
|
14
|
+
export class ContextHubCapability {
|
|
15
|
+
private readonly latestByProducer = new Map<string, ContextSegment>();
|
|
16
|
+
private readonly seen = new Set<string>();
|
|
17
|
+
|
|
18
|
+
/** Validates and records one contribution; silently drops a malformed, stale, or duplicate one without retaining its payload or crashing the caller. */
|
|
19
|
+
observe(payload: unknown, now = Date.now()): void {
|
|
20
|
+
try {
|
|
21
|
+
const contribution = validateContextContribution(payload, now);
|
|
22
|
+
const key = `${contribution.producerName}:${contribution.sequence}`;
|
|
23
|
+
if (this.seen.has(key)) return;
|
|
24
|
+
this.seen.add(key);
|
|
25
|
+
if (this.seen.size > CONTEXT_HUB_CONTRIBUTION_DEDUP_LIMIT) this.seen.delete(this.seen.values().next().value!);
|
|
26
|
+
this.latestByProducer.set(contribution.producerName, contribution.segment);
|
|
27
|
+
} catch {
|
|
28
|
+
// Reject malformed or stale cross-extension contributions without retaining payloads.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
contributedSegments(): ContextSegment[] {
|
|
33
|
+
return [...this.latestByProducer.values()];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
reset(): void {
|
|
37
|
+
this.latestByProducer.clear();
|
|
38
|
+
this.seen.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ContextSegment, ContextSegmentItem } from "@danypops/jittor";
|
|
2
|
+
|
|
3
|
+
export interface ContextReportUsage {
|
|
4
|
+
tokens: number | null;
|
|
5
|
+
contextWindow: number;
|
|
6
|
+
percent: number | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Bounds how many items render per segment -- a report is a scan-at-a-glance summary, not a full dump. */
|
|
10
|
+
const MAX_ITEMS_PER_SEGMENT_LINE = 5;
|
|
11
|
+
|
|
12
|
+
function formatTokens(tokens: number): string {
|
|
13
|
+
return tokens >= 1_000 ? `${(tokens / 1_000).toFixed(1)}k` : String(tokens);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function topItems(items: ContextSegmentItem[] | undefined): ContextSegmentItem[] {
|
|
17
|
+
return [...(items ?? [])].sort((left, right) => right.estimatedTokens - left.estimatedTokens).slice(0, MAX_ITEMS_PER_SEGMENT_LINE);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Plain-text Context Hub report: real provider-reported usage first (never a character-based
|
|
22
|
+
* estimate when real usage is available, matching Papyrus's own real-vs-estimate honesty
|
|
23
|
+
* accounting), then every segment heaviest-first with an explicit confidence tag
|
|
24
|
+
* (exact-tool/exact-cooperative/correlated/audited) so a reader never mistakes one attribution
|
|
25
|
+
* tier's certainty for another's.
|
|
26
|
+
*/
|
|
27
|
+
export function buildContextReport(segments: readonly ContextSegment[], usage: ContextReportUsage | undefined): string {
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
if (usage?.tokens !== null && usage?.tokens !== undefined) {
|
|
30
|
+
const percent = usage.percent !== null && usage.percent !== undefined ? ` (${usage.percent.toFixed(1)}%)` : "";
|
|
31
|
+
lines.push(`Real usage: ${formatTokens(usage.tokens)} / ${formatTokens(usage.contextWindow)} tokens${percent}`);
|
|
32
|
+
} else {
|
|
33
|
+
lines.push("Real usage: not yet reported -- sizes below are estimates only");
|
|
34
|
+
}
|
|
35
|
+
const sorted = [...segments].sort((left, right) => right.estimatedTokens - left.estimatedTokens);
|
|
36
|
+
if (sorted.length === 0) {
|
|
37
|
+
lines.push("", "(no segments observed yet)");
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
lines.push("");
|
|
41
|
+
for (const segment of sorted) {
|
|
42
|
+
lines.push(`${segment.label} — ${formatTokens(segment.estimatedTokens)} tok [${segment.confidence}]`);
|
|
43
|
+
for (const item of topItems(segment.items)) lines.push(` ${formatTokens(item.estimatedTokens)} tok ${item.label}`);
|
|
44
|
+
}
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
package/extension/src/footer.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
+
import { ProgressBar } from "malevich-tui-components";
|
|
4
5
|
import {
|
|
5
6
|
FOOTER_BAR_MAX_WIDTH,
|
|
6
7
|
FOOTER_BAR_MIN_WIDTH,
|
|
@@ -119,11 +120,11 @@ function barWidth(width: number): number {
|
|
|
119
120
|
return width >= FOOTER_WIDE_TERMINAL_WIDTH ? FOOTER_BAR_MAX_WIDTH : FOOTER_BAR_MIN_WIDTH;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
// Thin wrapper over Malevich's ProgressBar.format() -- keeps this file's own call sites
|
|
124
|
+
// (fraction-first, width-second, tolerant of null/non-finite) unchanged.
|
|
122
125
|
function progressBar(fraction: number | null, width: number): string {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const filled = Math.round(width * clamped);
|
|
126
|
-
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
126
|
+
const value = fraction === null || !Number.isFinite(fraction) ? 0 : fraction;
|
|
127
|
+
return new ProgressBar({ value, max: 1, width }).format(width);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
function fillColor(fraction: number | null): FooterColor {
|
package/extension/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
+
CONTEXT_HUB_CONTRIBUTION_CHANNEL,
|
|
3
4
|
FOOTER_COMPACTION_RENDER_INTERVAL_MS,
|
|
4
5
|
MAX_DYNAMIC_ROUTES,
|
|
5
6
|
PAPYRUS_CONTEXT_INJECTION_CHANNEL,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
validatePapyrusContextInjection,
|
|
11
12
|
applyTaskFocusEvent,
|
|
12
13
|
validateTaskFocusEvent,
|
|
14
|
+
toolLedgerSegment,
|
|
13
15
|
TASK_DOMAINS,
|
|
14
16
|
TASK_TYPES,
|
|
15
17
|
USAGE_PERIODS,
|
|
@@ -35,6 +37,8 @@ import { showUsagePanel } from "./usage.ts";
|
|
|
35
37
|
import { CodexRecoveryCapability, SYSTEM_RECOVERY_RUNTIME, type CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
36
38
|
import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
|
|
37
39
|
import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
|
|
40
|
+
import { ContextHubCapability } from "./capabilities/context-hub.ts";
|
|
41
|
+
import { buildContextReport } from "./context-report.ts";
|
|
38
42
|
|
|
39
43
|
export { formatFooterStatus } from "./tui.ts";
|
|
40
44
|
export type { CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
|
|
@@ -253,6 +257,8 @@ export function registerJittorExtension(
|
|
|
253
257
|
const localRunTelemetry = new LocalRunTelemetry();
|
|
254
258
|
const providerResponseTelemetry = new ProviderResponseTelemetry();
|
|
255
259
|
const codexRecoveryCapability = new CodexRecoveryCapability(pi, codexRecovery, recoveryRuntime);
|
|
260
|
+
const contextHub = new ContextHubCapability();
|
|
261
|
+
const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
|
|
256
262
|
const contextObservations = new Set<string>();
|
|
257
263
|
const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
|
|
258
264
|
try {
|
|
@@ -458,6 +464,15 @@ export function registerJittorExtension(
|
|
|
458
464
|
},
|
|
459
465
|
});
|
|
460
466
|
|
|
467
|
+
pi.registerCommand("context", {
|
|
468
|
+
description: "Context Hub: real usage plus every segment's estimated size (tool schemas by owning extension, and whatever other extensions contributed), each tagged with how it was attributed",
|
|
469
|
+
handler: async (_args, ctx) => {
|
|
470
|
+
const toolSegment = toolLedgerSegment(pi.getAllTools());
|
|
471
|
+
const segments = [toolSegment, ...contextHub.contributedSegments()];
|
|
472
|
+
ctx.ui.notify(buildContextReport(segments, ctx.getContextUsage()), "info");
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
461
476
|
pi.registerCommand("usage", {
|
|
462
477
|
description: "Cumulative token/cost usage graph with hourly/daily/weekly/monthly/quarterly views",
|
|
463
478
|
handler: async (args, ctx) => {
|
|
@@ -502,6 +517,7 @@ export function registerJittorExtension(
|
|
|
502
517
|
finishCompactionUi();
|
|
503
518
|
compactionTelemetry = new CompactionTelemetry();
|
|
504
519
|
localRunTelemetry.reset();
|
|
520
|
+
contextHub.reset();
|
|
505
521
|
cancelRecovery(true);
|
|
506
522
|
providerResponseTelemetry.resetTurn();
|
|
507
523
|
ctx.ui.setStatus("jittor", undefined);
|
|
@@ -642,6 +658,7 @@ export function registerJittorExtension(
|
|
|
642
658
|
if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
|
|
643
659
|
stopPapyrusContext?.();
|
|
644
660
|
stopPapyrusTaskFocus?.();
|
|
661
|
+
stopContextHub?.();
|
|
645
662
|
cancelRecovery(true);
|
|
646
663
|
localRunTelemetry.reset();
|
|
647
664
|
const session_id = ctx.sessionManager.getSessionId();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRetryingClient, type RetryingClient } from "@danypops/
|
|
1
|
+
import { createRetryingClient, type RetryingClient } from "@danypops/vehicle-client/daemon-client";
|
|
2
2
|
import { connectJittorClient, type JittorClient, type OperationInputs, type OperationName, type OperationOutputs } from "@danypops/jittor";
|
|
3
3
|
|
|
4
4
|
type JittorConnector = () => Promise<JittorClient>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-jittor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Pi extension for Jittor: native routing enforcement, footer, settings, usage graphs, and benchmark panels backed by the @danypops/jittor daemon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["pi-package", "llm-router", "token-budget"],
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"extensions": ["extension/src/index.ts"]
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@danypops/
|
|
16
|
-
"@danypops/jittor": "^0.
|
|
15
|
+
"@danypops/vehicle-client": "^0.1.1",
|
|
16
|
+
"@danypops/jittor": "^0.13.0",
|
|
17
|
+
"malevich-tui-components": "^0.2.0"
|
|
17
18
|
},
|
|
18
19
|
"peerDependencies": {
|
|
19
20
|
"@earendil-works/pi-coding-agent": "*",
|