pi-hypercharm-provider 1.2.3 → 1.3.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.
@@ -1,67 +1,67 @@
1
1
  {
2
2
  "format": 1,
3
3
  "entries": {
4
- "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": {
5
- "key": "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a",
4
+ "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": {
5
+ "key": "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85",
6
6
  "value": {
7
7
  "format": 1,
8
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
9
- "rootId": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
8
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
9
+ "rootId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
10
10
  "identity": {
11
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
11
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
12
12
  "name": "main",
13
13
  "kind": "main",
14
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86"
14
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
15
15
  },
16
- "startedAt": 1786913489401,
17
- "updatedAt": 1786913869536,
18
- "expiresAt": 1786913884536
16
+ "startedAt": 1786958035702,
17
+ "updatedAt": 1786958850932,
18
+ "expiresAt": 1786958865932
19
19
  },
20
- "version": 178,
21
- "updatedAt": 1786913869538,
20
+ "version": 196,
21
+ "updatedAt": 1786958850934,
22
22
  "updatedBy": {
23
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
23
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
24
24
  "name": "main",
25
25
  "kind": "main",
26
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86"
26
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
27
27
  }
28
28
  },
29
- "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86": {
30
- "key": "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
29
+ "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": {
30
+ "key": "sessions/01a00efe-4e23-7b2b-8583-2e8493780899",
31
31
  "value": {
32
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
33
- "name": "Peer 01a00c57",
32
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
33
+ "name": "Peer 01a00efe",
34
34
  "kind": "peer",
35
35
  "status": "running",
36
36
  "runner": "pi",
37
37
  "transport": "host",
38
38
  "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
39
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
40
- "model": "hypercharm/kimi-k3",
39
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899",
40
+ "model": "neuralwatt/kimi-k3",
41
41
  "thinking": "max",
42
- "startedAt": 1786913489401,
43
- "updatedAt": 1786913869536,
42
+ "startedAt": 1786958035702,
43
+ "updatedAt": 1786958850932,
44
44
  "pendingMessages": false,
45
45
  "local": false
46
46
  },
47
- "version": 178,
48
- "updatedAt": 1786913869540,
47
+ "version": 196,
48
+ "updatedAt": 1786958850935,
49
49
  "updatedBy": {
50
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
50
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
51
51
  "name": "main",
52
52
  "kind": "main",
53
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86"
53
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
54
54
  }
55
55
  },
56
- "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": {
57
- "key": "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a",
56
+ "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": {
57
+ "key": "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85",
58
58
  "value": {
59
59
  "format": 1,
60
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
60
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
61
61
  "kind": "root",
62
- "rootId": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
63
- "ownerHostId": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
64
- "ownerIdentityId": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
62
+ "rootId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
63
+ "ownerHostId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
64
+ "ownerIdentityId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
65
65
  "name": "main",
66
66
  "status": "running",
67
67
  "runner": "pi",
@@ -72,21 +72,21 @@
72
72
  "fabric"
73
73
  ],
74
74
  "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
75
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
76
- "model": "hypercharm/kimi-k3",
75
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899",
76
+ "model": "neuralwatt/kimi-k3",
77
77
  "thinking": "max",
78
- "startedAt": 1786913489401,
79
- "updatedAt": 1786913869536,
78
+ "startedAt": 1786958035702,
79
+ "updatedAt": 1786958850932,
80
80
  "pendingMessages": false,
81
81
  "controlProtocol": "v1"
82
82
  },
83
- "version": 178,
84
- "updatedAt": 1786913869543,
83
+ "version": 196,
84
+ "updatedAt": 1786958850936,
85
85
  "updatedBy": {
86
- "id": "session:01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
86
+ "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
87
87
  "name": "main",
88
88
  "kind": "main",
89
- "sessionId": "01a00c57-b7aa-7ea3-b78a-11e22d3cdd86"
89
+ "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
90
90
  }
91
91
  }
92
92
  },
@@ -97,9 +97,12 @@
97
97
  "topology/hosts/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f": 632,
98
98
  "sessions/019fe2d3-1ac4-7f63-a2e7-b396c7bb1738": 631,
99
99
  "topology/participants/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f": 632,
100
- "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 178,
101
- "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86": 178,
102
- "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 178
100
+ "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 217,
101
+ "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86": 216,
102
+ "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 217,
103
+ "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 196,
104
+ "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": 196,
105
+ "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 196
103
106
  },
104
107
  "tombstoneOrder": [
105
108
  "sessions/019fe2c8-1dd2-7435-9fa1-fa664831df18",
@@ -107,6 +110,9 @@
107
110
  "topology/hosts/e9758aa8c95305a8d60a8aa2c20fd53195591d479b2460e1273457793d3f1fec",
108
111
  "sessions/019fe2d3-1ac4-7f63-a2e7-b396c7bb1738",
109
112
  "topology/participants/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f",
110
- "topology/hosts/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f"
113
+ "topology/hosts/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f",
114
+ "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
115
+ "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a",
116
+ "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a"
111
117
  ]
112
118
  }
package/index.ts CHANGED
@@ -41,6 +41,10 @@
41
41
  * again on pi's agent_settled event (fires only once no automatic retry,
42
42
  * compaction, or queued continuation can follow) — and nowhere else, so
43
43
  * sessions without HyperCharm turns make zero status-related API calls.
44
+ * Between polls the balance moves optimistically: each turn's
45
+ * usage.cost.hypercredits is deducted from the last /v1/credits value at
46
+ * turn_end so the account line tracks spend live; the agent_settled poll
47
+ * reconciles any drift.
44
48
  *
45
49
  * Unit note (observed): 20 hypercredits = $1. usage.cost.hypercredits is in
46
50
  * the same display unit /v1/credits reports; usage.cost.usd ÷ 20 matches.
@@ -92,6 +96,7 @@ import customModelsData from "./custom-models.json" with { type: "json" };
92
96
  import patchData from "./patch.json" with { type: "json" };
93
97
  import deprecatedData from "./deprecated-models.json" with { type: "json" };
94
98
  import {
99
+ applyOptimisticSpend,
95
100
  buildAccountTiers,
96
101
  buildSessionLine,
97
102
  coerceStatusConfig,
@@ -772,6 +777,13 @@ function commitPending(ctx: ExtensionContext): void {
772
777
  if (!pendingSawUsage && pendingRequests === 0) return;
773
778
  sessionStats.requests += pendingRequests;
774
779
  sessionStats.spendHc += pendingSpendHc;
780
+
781
+ // Optimistic balance: deduct this turn's observed spend so the account
782
+ // line ticks down per turn with zero extra API calls. Every credits poll
783
+ // overwrites account.balance (never adjusts), so this cannot
784
+ // double-count; the agent_settled poll reconciles any drift.
785
+ applyOptimisticSpend(account, pendingSpendHc);
786
+
775
787
  pendingRequests = 0;
776
788
  pendingSpendHc = 0;
777
789
  pendingSawUsage = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hypercharm-provider",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "HyperCharm provider extension for pi - Access DeepSeek, GLM, Kimi, Qwen, MiniMax, Gemma, and GPT-OSS models through the Charm Hyper API",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/status.ts CHANGED
@@ -96,6 +96,19 @@ export interface SessionStats {
96
96
 
97
97
  export const EMPTY_SESSION_STATS: SessionStats = { requests: 0, spendHc: 0 };
98
98
 
99
+ /**
100
+ * Optimistically deduct observed turn spend from the last polled balance.
101
+ * Safe against double-counting only because callers overwrite (never adjust)
102
+ * `balance` on every credits poll — the agent_settled poll reconciles drift.
103
+ * Unknown balances stay unknown; estimates clamp at 0 (real exhaustion is
104
+ * still signaled by the 402 path, not by an estimated zero).
105
+ */
106
+ export function applyOptimisticSpend(acc: AccountState, spendHc: number): void {
107
+ if (spendHc > 0 && acc.balance !== null) {
108
+ acc.balance = Math.max(0, acc.balance - spendHc);
109
+ }
110
+ }
111
+
99
112
  // ─── Formatters ───────────────────────────────────────────────────────────────
100
113
 
101
114
  function trimZeros(text: string): string {
@@ -9,6 +9,7 @@ import {
9
9
  EMPTY_ACCOUNT,
10
10
  StatusLineWidget,
11
11
  accountHasData,
12
+ applyOptimisticSpend,
12
13
  buildAccountTiers,
13
14
  buildSessionLine,
14
15
  coerceStatusConfig,
@@ -65,6 +66,20 @@ const balOnly = buildAccountTiers(acc({ balance: 12 }), true);
65
66
  assert.equal(balOnly[0], "⚠ ◆ 12 hc");
66
67
  assert.ok(balOnly.includes("12 hc"));
67
68
 
69
+ // optimistic spend deduction
70
+ {
71
+ const opt = acc({ balance: 249 });
72
+ applyOptimisticSpend(opt, 0.5);
73
+ assert.equal(opt.balance, 248.5);
74
+ applyOptimisticSpend(opt, 0); // zero spend is a no-op
75
+ assert.equal(opt.balance, 248.5);
76
+ applyOptimisticSpend(opt, 300); // clamps at 0, never negative
77
+ assert.equal(opt.balance, 0);
78
+ const unknown = acc({ balance: null });
79
+ applyOptimisticSpend(unknown, 1); // unknown balance stays unknown
80
+ assert.equal(unknown.balance, null);
81
+ }
82
+
68
83
  // ── width math ──
69
84
  assert.equal(termVisWidth("abc"), 3);
70
85
  assert.equal(termVisWidth(""), 0);