@oh-my-pi/pi-ai 18.4.10 → 18.4.11

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.4.11] - 2026-10-02
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Cursor cached prompt token accounting to prevent duplicate input-token and cost reporting on cached turns.
10
+ - Fixed auth-broker credential handling so late token-refresh responses cannot restore logged-out credentials or overwrite a newer login.
11
+
5
12
  ## [18.4.10] - 2026-10-02
6
13
 
7
14
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "18.4.10",
3
+ "version": "18.4.11",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "keywords": [
6
6
  "ai",
@@ -155,11 +155,11 @@
155
155
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
156
156
  },
157
157
  "dependencies": {
158
- "@oh-my-pi/omptype": "18.4.10",
159
- "@oh-my-pi/pi-catalog": "18.4.10",
160
- "@oh-my-pi/pi-natives": "18.4.10",
161
- "@oh-my-pi/pi-utils": "18.4.10",
162
- "@oh-my-pi/pi-wire": "18.4.10"
158
+ "@oh-my-pi/omptype": "18.4.11",
159
+ "@oh-my-pi/pi-catalog": "18.4.11",
160
+ "@oh-my-pi/pi-natives": "18.4.11",
161
+ "@oh-my-pi/pi-utils": "18.4.11",
162
+ "@oh-my-pi/pi-wire": "18.4.11"
163
163
  },
164
164
  "devDependencies": {
165
165
  "@types/bun": "^1.3.14"
@@ -17,6 +17,7 @@ import {
17
17
  type OAuthCredential,
18
18
  type OAuthRefreshReason,
19
19
  REMOTE_REFRESH_SENTINEL,
20
+ type RemoteOAuthCredential,
20
21
  type StoredAuthCredential,
21
22
  type StoredCredentialBlock,
22
23
  } from "../auth/types";
@@ -849,15 +850,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
849
850
 
850
851
  async markCredentialSuspect(credentialId: number, opts: { signal?: AbortSignal } = {}): Promise<void> {
851
852
  this.#noteActivity();
852
- const { entry } = await this.#client.refreshCredential(credentialId, opts.signal, "auth-recovery");
853
- if (entry.credential.type !== "oauth") {
854
- throw new AIError.AuthBrokerError(`Broker returned non-OAuth credential for id=${credentialId}`);
855
- }
856
- if (!this.#applyCredentialEntry(entry)) {
857
- throw new AIError.AuthBrokerError(
858
- `Broker refreshed credential id=${credentialId} outside the configured account pool`,
859
- );
860
- }
853
+ await this.#refreshThroughBroker(credentialId, opts.signal, "auth-recovery");
861
854
  this.#maybeRefreshSnapshot("suspect credential refresh");
862
855
  }
863
856
 
@@ -1168,21 +1161,12 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1168
1161
  reason?: OAuthRefreshReason,
1169
1162
  ): Promise<OAuthCredentials> {
1170
1163
  this.#noteActivity();
1171
- const { entry } = await this.#client.refreshCredential(credentialId, signal, reason);
1172
- if (entry.credential.type !== "oauth") {
1173
- throw new AIError.AuthBrokerError(`Broker returned non-OAuth credential for id=${credentialId}`);
1174
- }
1175
- if (!this.#applyCredentialEntry(entry)) {
1176
- throw new AIError.AuthBrokerError(
1177
- `Broker refreshed credential id=${credentialId} outside the configured account pool`,
1178
- );
1179
- }
1164
+ const refreshed = await this.#refreshThroughBroker(credentialId, signal, reason);
1180
1165
  if (!this.#streamingActive) {
1181
1166
  await this.refreshSnapshot().catch(error => {
1182
1167
  logger.debug("auth-broker snapshot refresh after credential refresh failed", { error: String(error) });
1183
1168
  });
1184
1169
  }
1185
- const refreshed = entry.credential;
1186
1170
  return {
1187
1171
  access: refreshed.access,
1188
1172
  refresh: REMOTE_REFRESH_SENTINEL,
@@ -1194,6 +1178,38 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
1194
1178
  };
1195
1179
  }
1196
1180
 
1181
+ /**
1182
+ * Refresh one credential through the broker and apply the reply. If this
1183
+ * client's copy changed to something else while the request was in flight,
1184
+ * the reply may be stale, so the broker's current row wins: a logout stays
1185
+ * logged out and a newer login is kept.
1186
+ */
1187
+ async #refreshThroughBroker(
1188
+ credentialId: number,
1189
+ signal?: AbortSignal,
1190
+ reason?: OAuthRefreshReason,
1191
+ ): Promise<RemoteOAuthCredential> {
1192
+ const local = () => this.#snapshot.credentials.find(candidate => candidate.id === credentialId);
1193
+ const before = JSON.stringify(local()?.credential);
1194
+ let { entry } = await this.#client.refreshCredential(credentialId, signal, reason);
1195
+ const current = JSON.stringify(local()?.credential);
1196
+ if (current !== before && current !== JSON.stringify(entry.credential)) {
1197
+ await this.refreshSnapshot();
1198
+ const latest = local();
1199
+ if (!latest) throw new AIError.AuthBrokerError(`Credential id=${credentialId} was removed during refresh`);
1200
+ entry = latest;
1201
+ }
1202
+ if (entry.credential.type !== "oauth") {
1203
+ throw new AIError.AuthBrokerError(`Broker returned non-OAuth credential for id=${credentialId}`);
1204
+ }
1205
+ if (!this.#applyCredentialEntry(entry)) {
1206
+ throw new AIError.AuthBrokerError(
1207
+ `Broker refreshed credential id=${credentialId} outside the configured account pool`,
1208
+ );
1209
+ }
1210
+ return entry.credential;
1211
+ }
1212
+
1197
1213
  /**
1198
1214
  * Store-level hook consumed by `AuthStorage.usage.reports()` — proxies
1199
1215
  * to the broker's `/v1/usage` endpoint. Shared per-credential caches and
@@ -5341,7 +5341,9 @@ export function processInteractionUpdate(
5341
5341
  * summed 22 against a final 36 — and never report input, cache, or reasoning
5342
5342
  * tokens, so every bucket the final frame reports replaces the streamed
5343
5343
  * estimate. Unreported counters decode as `undefined`; a frame that reports
5344
- * nothing at all leaves the streamed totals untouched.
5344
+ * nothing at all leaves the streamed totals untouched. `inputTokens` counts the
5345
+ * whole prompt, cache hits and writes included, so fresh input is what remains
5346
+ * after both are taken out.
5345
5347
  */
5346
5348
  function applyTurnEndedUsage(usage: Usage, update: TurnEndedUpdate): void {
5347
5349
  const input = Number(update.inputTokens ?? 0n);
@@ -5350,7 +5352,7 @@ function applyTurnEndedUsage(usage: Usage, update: TurnEndedUpdate): void {
5350
5352
  const cacheWrite = Number(update.cacheWriteTokens ?? 0n);
5351
5353
  const reasoning = Number(update.reasoningTokens ?? 0n);
5352
5354
  if (input <= 0 && output <= 0 && cacheRead <= 0 && cacheWrite <= 0) return;
5353
- if (input > 0) usage.input = input;
5355
+ if (input > 0) usage.input = Math.max(input - cacheRead - cacheWrite, 0);
5354
5356
  if (output > 0) usage.output = output;
5355
5357
  if (cacheRead > 0) usage.cacheRead = cacheRead;
5356
5358
  if (cacheWrite > 0) usage.cacheWrite = cacheWrite;