@yunazgr/pi-companion 0.3.2 → 0.3.3

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/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Lightweight, local-first remote control for Pi sessions.
4
4
 
5
- **New in 0.3.2:** responsive automation and run-history tables, rounded status chips, matching clay-icon headers, and a single-column automation workspace with full-width mobile controls. **Edit automation** scrolls to and focuses the inline editor. Pull to refresh Overview, Sessions, Automations, or Settings—or use the Refresh button; unsaved settings stay intact. Camera-policy guidance now explains HTTPS proxy configuration. Existing rich results, staged job editing, optional retries, and answers-only automation sessions are retained. See [automations](docs/automations.md).
5
+ **New in 0.3.3:** Provider usage now shows measured session tokens and cost without pretending they are subscription quotas. Compatible extensions can still supply real weekly/5-hour limits. Mobile sessions, automations and usage use cards; the overview has compact activity filters; automation buttons share a consistent style; pull-to-refresh is gesture-only. No data or provider credentials are collected by the daemon outside the already shared session metadata.
6
+
7
+ **New in 0.3.2:** responsive automation and run-history tables, rounded status chips, matching clay-icon headers, and a single-column automation workspace with full-width mobile controls. **Edit automation** scrolls to and focuses the inline editor. Pull to refresh Overview, Sessions, Automations, or Settings—with a gesture; unsaved settings stay intact. Camera-policy guidance now explains HTTPS proxy configuration. Existing rich results, staged job editing, optional retries, and answers-only automation sessions are retained. See [automations](docs/automations.md).
6
8
 
7
9
  Pi Companion is deliberately not another agent runtime. Pi owns execution and conversation state. A single Rust daemon owns session discovery, pairing, temporary file exchange, browser fan-out, and the embedded web UI.
8
10
 
@@ -192,7 +194,7 @@ Network loss and daemon restarts reconnect automatically with bounded backoff. H
192
194
 
193
195
  Shared session title, model, effort and working directory refresh on Pi events and once per second while sharing, including while idle. Session detail shows context-window usage and estimated session cost. Native Pi context and recorded usage costs take precedence; independent extension reports fill unavailable fields. Missing usage is shown as unavailable, not zero.
194
196
 
195
- **Settings → Usage** is available on the console and paired devices. It displays the latest snapshot per provider: weekly usage, 5-hour usage when reported, reset times, source and snapshot age. Quota snapshots are not added across sessions or accounts. These are extension-reported limits, not billing totals or locally inferred quotas; providers without a quota report remain unavailable.
197
+ **Settings → Usage** is available on the console and paired devices. It displays the latest snapshot per provider: weekly usage, 5-hour usage when reported, reset times, source and snapshot age. Quota snapshots are not added across sessions or accounts. Those limits are extension-reported, not billing totals or locally inferred quotas. Companion additionally reports actual session token consumption and cost grouped by the model's provider from Pi's recorded assistant messages; this is session-only data, not an account-wide tally. Settings selects the newest consumption independently of quota freshness and displays their timestamps and session-ended indicators separately. When no quota adapter is installed, 5-hour and weekly quotas still show as unavailable rather than fabricated percentages.
196
198
 
197
199
  Extensions can publish a provider-neutral event without importing Companion:
198
200
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunazgr/pi-companion",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Local-first remote control for Pi sessions: live activity feed, steering, git diff, file drop and phone pairing from a single Rust daemon.",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/protocol.ts CHANGED
@@ -43,6 +43,11 @@ export type ProviderUsage = {
43
43
  updatedAt: string;
44
44
  weekly?: UsageWindow;
45
45
  fiveHour?: UsageWindow;
46
+ /** Observed consumption in this shared Pi session, not account quota utilization. */
47
+ sessionTokens?: number;
48
+ sessionCost?: number;
49
+ /** Native consumption freshness, independent of the account quota report. */
50
+ sessionUpdatedAt?: string;
46
51
  };
47
52
  export type SessionTelemetry = {
48
53
  context?: { tokens?: number; window?: number; percent?: number; source?: string };
package/src/telemetry.ts CHANGED
@@ -51,6 +51,7 @@ export function normalizeTelemetry(value: unknown, source = "extension", now = D
51
51
  /** Independent sources may supply different fields; one broken/missing adapter cannot erase another. */
52
52
  export class TelemetryRelay {
53
53
  private sources = new Map<string, { telemetry: SessionTelemetry; contextAt?: number; costAt?: number }>();
54
+ private nativeUsage = new Map<string, { fingerprint: string; updatedAt: string }>();
54
55
 
55
56
  ingest(value: unknown, fallbackSource = "extension", now = Date.now()) {
56
57
  const raw = record(value);
@@ -140,6 +141,43 @@ export class TelemetryRelay {
140
141
  }
141
142
  if (known && Number.isFinite(total)) result.cost = { amount: total, currency: "USD", source: "native" };
142
143
  } catch { /* Preserve extension estimate when native usage is unavailable. */ }
144
+ // Native session entries are available without another plugin. They report actual
145
+ // consumption, not provider subscription limits, and must never fabricate quotas.
146
+ try {
147
+ const totals = new Map<string, { tokens: number; cost: number; hasTokens: boolean; hasCost: boolean }>();
148
+ for (const entry of ctx?.sessionManager?.getEntries?.() ?? []) {
149
+ const raw = record(entry);
150
+ if (raw?.type !== "message") continue;
151
+ const message = record(raw.message);
152
+ if (message?.role !== "assistant") continue;
153
+ const provider = text(message.provider);
154
+ const usage = record(message.usage);
155
+ if (!provider || !usage) continue;
156
+ const pieces = [usage.input, usage.output, usage.cacheRead, usage.cacheWrite].map(number);
157
+ const tokens = number(usage.totalTokens) ?? (pieces.some(value => value !== undefined)
158
+ ? pieces.reduce<number>((sum, value) => sum + (value ?? 0), 0) : undefined);
159
+ const cost = number(record(usage.cost)?.total);
160
+ if (tokens === undefined && cost === undefined) continue;
161
+ const total = totals.get(provider) ?? { tokens: 0, cost: 0, hasTokens: false, hasCost: false };
162
+ if (tokens !== undefined) { total.tokens += tokens; total.hasTokens = true; }
163
+ if (cost !== undefined) { total.cost += cost; total.hasCost = true; }
164
+ totals.set(provider, total);
165
+ }
166
+ for (const [provider, total] of totals) {
167
+ const fingerprint = JSON.stringify(total);
168
+ const cached = this.nativeUsage.get(provider);
169
+ const updatedAt = cached?.fingerprint === fingerprint ? cached.updatedAt : new Date(now).toISOString();
170
+ this.nativeUsage.set(provider, { fingerprint, updatedAt });
171
+ const old = providers.get(provider);
172
+ providers.set(provider, {
173
+ ...old, provider, source: old?.source ?? "Pi session", updatedAt: old?.updatedAt ?? updatedAt,
174
+ sessionUpdatedAt: updatedAt,
175
+ ...(total.hasTokens ? { sessionTokens: total.tokens } : {}),
176
+ ...(total.hasCost ? { sessionCost: total.cost } : {})
177
+ });
178
+ }
179
+ if (providers.size) result.providers = [...providers.values()];
180
+ } catch { /* Keep independently reported quota snapshots if native entries are unavailable. */ }
143
181
  return result;
144
182
  }
145
183
  }